diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f031e8e..dd52ad7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## Breaking change + +- Removed ThirdPartyEmailPassword and ThirdPartyPasswordless recipes. Instead, you should use ThirdParty + EmailPassword or ThirdParty + Passwordless recipes separately in your recipe list. +- Removed `rid` query param from: + - email verification links + - passwordless magic links + - password reset links + +## Changes + +- If `rid` header is present in an API call, the routing no only only depends on that. If the SDK cannot resolve a request handler based on the `rid`, request path and method, it will try to resolve a request handler only based on the request path and method (therefore ignoring the `rid` header). +- New API handlers are: + - `GET /emailpassword/email/exists` => email password, does email exist API (used to be `GET /signup/email/exists` with `rid` of `emailpassword` or `thirdpartyemailpassword` which is now deprecated) + - `GET /passwordless/email/exists` => email password, does email exist API (used to be `GET /signup/email/exists` with `rid` of `passwordless` or `thirdpartypasswordless` which is now deprecated) + - `GET /passwordless/phonenumber/exists` => email password, does email exist API (used to be `GET /signup/phonenumber/exists` which is now deprecated) +- Support for FDI 2.0 + +## Migration guide + +- If you were using `ThirdPartyEmailPassword`, you should now init `ThirdParty` and `EmailPassword` recipes separately. The config for the individual recipes are mostly the same, except the syntax may be different. Check our recipe guides for [ThirdParty](https://supertokens.com/docs/thirdparty/introduction) and [EmailPassword](https://supertokens.com/docs/emailpassword/introduction) for more information. + +- If you were using `ThirdPartyPasswordless`, you should now init `ThirdParty` and `Passwordless` recipes separately. The config for the individual recipes are mostly the same, except the syntax may be different. Check our recipe guides for [ThirdParty](https://supertokens.com/docs/thirdparty/introduction) and [Passwordless](https://supertokens.com/docs/passwordless/introduction) for more information. + + ## [0.17.5] - 2024-03-14 - Adds a type uint64 to the `accessTokenCookiesExpiryDurationMillis` local variable in `recipe/session/utils.go`. It also removes the redundant `uint64` type forcing needed because of the untyped variable. - Fixes the passing of `tenantId` in `getAllSessionHandlesForUser` and `revokeAllSessionsForUser` based on `fetchAcrossAllTenants` and `revokeAcrossAllTenants` inputs respectively. diff --git a/recipe/emailpassword/api/implementation.go b/recipe/emailpassword/api/implementation.go index 209fd5dd..09186496 100644 --- a/recipe/emailpassword/api/implementation.go +++ b/recipe/emailpassword/api/implementation.go @@ -68,7 +68,6 @@ func MakeAPIImplementation() epmodels.APIInterface { passwordResetLink, err := GetPasswordResetLink( options.AppInfo, - options.RecipeID, response.OK.Token, tenantId, options.Req, diff --git a/recipe/emailpassword/api/utils.go b/recipe/emailpassword/api/utils.go index fa6db009..a96faf94 100644 --- a/recipe/emailpassword/api/utils.go +++ b/recipe/emailpassword/api/utils.go @@ -126,17 +126,16 @@ func validateFormOrThrowError(configFormFields []epmodels.NormalisedFormField, i return nil } -func GetPasswordResetLink(appInfo supertokens.NormalisedAppinfo, recipeID string, token string, tenantId string, request *http.Request, userContext supertokens.UserContext) (string, error) { +func GetPasswordResetLink(appInfo supertokens.NormalisedAppinfo, token string, tenantId string, request *http.Request, userContext supertokens.UserContext) (string, error) { websiteDomain, err := appInfo.GetOrigin(request, userContext) if err != nil { return "", err } return fmt.Sprintf( - "%s%s/reset-password?token=%s&rid=%s&tenantId=%s", + "%s%s/reset-password?token=%s&tenantId=%s", websiteDomain.GetAsStringDangerous(), appInfo.WebsiteBasePath.GetAsStringDangerous(), token, - recipeID, tenantId, ), nil } diff --git a/recipe/emailpassword/authFlow_test.go b/recipe/emailpassword/authFlow_test.go index 720d973f..3dfe088d 100644 --- a/recipe/emailpassword/authFlow_test.go +++ b/recipe/emailpassword/authFlow_test.go @@ -32,10 +32,117 @@ import ( "github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" "github.com/supertokens/supertokens-golang/supertokens" "github.com/supertokens/supertokens-golang/test/unittesting" ) +func TestRightRidButRecipeMissingReturns404(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + thirdparty.Init(&tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-test-id", + ClientSecret: "test-secret", + }, + }, + }, + }, + }, + }, + }), + }, + } + + BeforeEach() + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + res, err := unittesting.SignInRequest("random@gmail.com", "validpass123", testServer.URL) + + if err != nil { + t.Error(err.Error()) + } + + assert.NoError(t, err) + assert.Equal(t, 404, res.StatusCode) +} + +func TestSignInWorksWithThirdPartyEmailPasswordRid(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + thirdparty.Init(&tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-test-id", + ClientSecret: "test-secret", + }, + }, + }, + }, + }, + }, + }), + Init(nil), + }, + } + + BeforeEach() + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + res, err := unittesting.SignInRequestWithThirdpartyemailpasswordRid("random@gmail.com", "validpass123", testServer.URL) + + if err != nil { + t.Error(err.Error()) + } + + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) +} + // SigninFeature Tests func TestDisablingAPIDefaultSigninDoesNotWork(t *testing.T) { configValue := supertokens.TypeInput{ diff --git a/recipe/emailpassword/constants/constants.go b/recipe/emailpassword/constants/constants.go index 185be214..c27d8506 100644 --- a/recipe/emailpassword/constants/constants.go +++ b/recipe/emailpassword/constants/constants.go @@ -20,5 +20,6 @@ const ( SignInAPI = "/signin" GeneratePasswordResetTokenAPI = "/user/password/reset/token" PasswordResetAPI = "/user/password/reset" - SignupEmailExistsAPI = "/signup/email/exists" + SignupEmailExistsAPIOld = "/signup/email/exists" + SignupEmailExistsAPI = "/emailpassword/email/exists" ) diff --git a/recipe/emailpassword/emailExistsAndVerificationCheck_test.go b/recipe/emailpassword/emailExistsAndVerificationCheck_test.go index 346f29e1..08a84657 100644 --- a/recipe/emailpassword/emailExistsAndVerificationCheck_test.go +++ b/recipe/emailpassword/emailExistsAndVerificationCheck_test.go @@ -35,12 +35,139 @@ import ( "github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels" "github.com/supertokens/supertokens-golang/recipe/emailverification" "github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels" + "github.com/supertokens/supertokens-golang/recipe/passwordless" + "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" "github.com/supertokens/supertokens-golang/supertokens" "github.com/supertokens/supertokens-golang/test/unittesting" ) +func TestEmailExistsPicksRightRecipeDependingOnRid(t *testing.T) { + passwordlessEmailExists := false + emailpasswordEmailExists := false + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + Init(&epmodels.TypeInput{ + Override: &epmodels.OverrideStruct{ + APIs: func(originalImplementation epmodels.APIInterface) epmodels.APIInterface { + oEmailExists := *originalImplementation.EmailExistsGET + (*originalImplementation.EmailExistsGET) = func(email, tenantId string, options epmodels.APIOptions, userContext supertokens.UserContext) (epmodels.EmailExistsGETResponse, error) { + emailpasswordEmailExists = true + return oEmailExists(email, tenantId, options, userContext) + } + + return originalImplementation + }, + }, + }), + passwordless.Init(plessmodels.TypeInput{ + ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ + Enabled: true, + }, + FlowType: "USER_INPUT_CODE", + Override: &plessmodels.OverrideStruct{ + APIs: func(originalImplementation plessmodels.APIInterface) plessmodels.APIInterface { + oEmailExists := *originalImplementation.EmailExistsGET + (*originalImplementation.EmailExistsGET) = func(email, tenantId string, options plessmodels.APIOptions, userContext supertokens.UserContext) (plessmodels.EmailExistsGETResponse, error) { + passwordlessEmailExists = true + return oEmailExists(email, tenantId, options, userContext) + } + + return originalImplementation + }, + }, + }), + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + }, + } + + BeforeEach() + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + { + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/signup/email/exists", nil) + q := req.URL.Query() + q.Add("email", "random@email.com") + req.Header.Add("rid", "emailpassword") + req.URL.RawQuery = q.Encode() + assert.NoError(t, err) + res, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) + assert.True(t, emailpasswordEmailExists) + assert.False(t, passwordlessEmailExists) + } + + { + emailpasswordEmailExists = false + passwordlessEmailExists = false + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/signup/email/exists", nil) + q := req.URL.Query() + q.Add("email", "random@email.com") + req.Header.Add("rid", "passwordless") + req.URL.RawQuery = q.Encode() + assert.NoError(t, err) + res, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) + assert.False(t, emailpasswordEmailExists) + assert.True(t, passwordlessEmailExists) + } + + { + emailpasswordEmailExists = false + passwordlessEmailExists = false + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/signup/email/exists", nil) + q := req.URL.Query() + q.Add("email", "random@email.com") + req.Header.Add("rid", "thirdpartypasswordless") + req.URL.RawQuery = q.Encode() + assert.NoError(t, err) + res, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) + assert.False(t, emailpasswordEmailExists) + assert.True(t, passwordlessEmailExists) + } + + { + emailpasswordEmailExists = false + passwordlessEmailExists = false + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/signup/email/exists", nil) + q := req.URL.Query() + q.Add("email", "random@email.com") + req.Header.Add("rid", "thirdpartyemailpassword") + req.URL.RawQuery = q.Encode() + assert.NoError(t, err) + res, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) + assert.True(t, emailpasswordEmailExists) + assert.False(t, passwordlessEmailExists) + } +} + // Email exists tests func TestEmailExistGetStopsWorkingWhenDisabled(t *testing.T) { configValue := supertokens.TypeInput{ @@ -172,6 +299,91 @@ func TestGoodInputsEmailExists(t *testing.T) { } +func TestGoodInputsEmailExistsNewPath(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + Init(nil), + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + }, + } + + BeforeEach() + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + passwordVal := "validPass123" + + emailVal := "random@email.com" + + formFields := map[string][]map[string]string{ + "formFields": { + { + "id": "email", + "value": emailVal, + }, + { + "id": "password", + "value": passwordVal, + }, + }, + } + + postBody, err := json.Marshal(formFields) + if err != nil { + t.Error(err.Error()) + } + + resp, err := http.Post(testServer.URL+"/auth/signup", "application/json", bytes.NewBuffer(postBody)) + + assert.Equal(t, 200, resp.StatusCode) + + assert.NoError(t, err) + data, _ := io.ReadAll(resp.Body) + resp.Body.Close() + var response map[string]interface{} + _ = json.Unmarshal(data, &response) + + assert.Equal(t, "OK", response["status"]) + + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/emailpassword/email/exists", nil) + q := req.URL.Query() + q.Add("email", "random@email.com") + req.URL.RawQuery = q.Encode() + assert.NoError(t, err) + res, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, 200, res.StatusCode) + + data2, err := io.ReadAll(res.Body) + assert.NoError(t, err) + res.Body.Close() + var response2 map[string]interface{} + _ = json.Unmarshal(data2, &response2) + + assert.Equal(t, "OK", response2["status"]) + assert.Equal(t, true, response2["exists"]) + +} + func TestGoodInputsEmailDoesNotExists(t *testing.T) { configValue := supertokens.TypeInput{ Supertokens: &supertokens.ConnectionInfo{ diff --git a/recipe/emailpassword/main.go b/recipe/emailpassword/main.go index e157d16a..45ae26b8 100644 --- a/recipe/emailpassword/main.go +++ b/recipe/emailpassword/main.go @@ -140,7 +140,6 @@ func CreateResetPasswordLink(tenantId string, userID string, userContext ...supe link, err := api.GetPasswordResetLink( instance.RecipeModule.GetAppInfo(), - instance.RecipeModule.GetRecipeID(), tokenResponse.OK.Token, tenantId, supertokens.GetRequestFromUserContext(userContext[0]), diff --git a/recipe/emailpassword/passwordReset_test.go b/recipe/emailpassword/passwordReset_test.go index 45d7d39d..abec9ce8 100644 --- a/recipe/emailpassword/passwordReset_test.go +++ b/recipe/emailpassword/passwordReset_test.go @@ -128,7 +128,7 @@ func TestEmailValidationCheckInGenerateTokenAPI(t *testing.T) { assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, "https://supertokens.io/auth/reset-password", resetURL) assert.NotEmpty(t, tokenInfo) - assert.True(t, strings.HasPrefix(ridInfo, "emailpassword")) + assert.True(t, strings.HasPrefix(ridInfo, "")) } func TestPasswordValidation(t *testing.T) { @@ -556,5 +556,5 @@ func TestPasswordResetLinkUsesOriginFunctionIfProvided(t *testing.T) { assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, "http://localhost:2000/auth/reset-password", resetURL) assert.NotEmpty(t, tokenInfo) - assert.True(t, strings.HasPrefix(ridInfo, "emailpassword")) + assert.True(t, strings.HasPrefix(ridInfo, "")) } diff --git a/recipe/emailpassword/recipe.go b/recipe/emailpassword/recipe.go index 0d501ccc..3fa20df4 100644 --- a/recipe/emailpassword/recipe.go +++ b/recipe/emailpassword/recipe.go @@ -120,6 +120,10 @@ func (r *Recipe) getAPIsHandled() ([]supertokens.APIHandled, error) { if err != nil { return nil, err } + signupEmailExistsAPIOld, err := supertokens.NewNormalisedURLPath(constants.SignupEmailExistsAPIOld) + if err != nil { + return nil, err + } signupEmailExistsAPI, err := supertokens.NewNormalisedURLPath(constants.SignupEmailExistsAPI) if err != nil { return nil, err @@ -144,6 +148,11 @@ func (r *Recipe) getAPIsHandled() ([]supertokens.APIHandled, error) { PathWithoutAPIBasePath: passwordResetAPI, ID: constants.PasswordResetAPI, Disabled: r.APIImpl.PasswordResetPOST == nil, + }, { + Method: http.MethodGet, + PathWithoutAPIBasePath: signupEmailExistsAPIOld, + ID: constants.SignupEmailExistsAPIOld, + Disabled: r.APIImpl.EmailExistsGET == nil, }, { Method: http.MethodGet, PathWithoutAPIBasePath: signupEmailExistsAPI, @@ -171,7 +180,7 @@ func (r *Recipe) handleAPIRequest(id string, tenantId string, req *http.Request, return api.GeneratePasswordResetToken(r.APIImpl, tenantId, options, userContext) } else if id == constants.PasswordResetAPI { return api.PasswordReset(r.APIImpl, tenantId, options, userContext) - } else if id == constants.SignupEmailExistsAPI { + } else if id == constants.SignupEmailExistsAPIOld || id == constants.SignupEmailExistsAPI { return api.EmailExists(r.APIImpl, tenantId, options, userContext) } return defaultErrors.New("should never come here") diff --git a/recipe/emailverification/api/implementation.go b/recipe/emailverification/api/implementation.go index 5c144b13..f0493742 100644 --- a/recipe/emailverification/api/implementation.go +++ b/recipe/emailverification/api/implementation.go @@ -126,7 +126,6 @@ func MakeAPIImplementation() evmodels.APIInterface { emailVerificationURL, err := GetEmailVerifyLink( options.AppInfo, response.OK.Token, - options.RecipeID, sessionContainer.GetTenantIdWithContext(userContext), options.Req, userContext, diff --git a/recipe/emailverification/api/utils.go b/recipe/emailverification/api/utils.go index b18490c4..c9a4190e 100644 --- a/recipe/emailverification/api/utils.go +++ b/recipe/emailverification/api/utils.go @@ -7,17 +7,16 @@ import ( "github.com/supertokens/supertokens-golang/supertokens" ) -func GetEmailVerifyLink(appInfo supertokens.NormalisedAppinfo, token string, recipeID string, tenantId string, request *http.Request, userContext supertokens.UserContext) (string, error) { +func GetEmailVerifyLink(appInfo supertokens.NormalisedAppinfo, token string, tenantId string, request *http.Request, userContext supertokens.UserContext) (string, error) { websiteDomain, err := appInfo.GetOrigin(request, userContext) if err != nil { return "", err } return fmt.Sprintf( - "%s%s/verify-email?token=%s&rid=%s&tenantId=%s", + "%s%s/verify-email?token=%s&tenantId=%s", websiteDomain.GetAsStringDangerous(), appInfo.WebsiteBasePath.GetAsStringDangerous(), token, - recipeID, tenantId, ), nil } diff --git a/recipe/emailverification/emailverification_email_test.go b/recipe/emailverification/emailverification_email_test.go index ae210578..57d26e90 100644 --- a/recipe/emailverification/emailverification_email_test.go +++ b/recipe/emailverification/emailverification_email_test.go @@ -18,6 +18,7 @@ package emailverification import ( "net/http" + "net/url" "testing" "github.com/stretchr/testify/assert" @@ -81,6 +82,7 @@ func TestBackwardCompatibilityServiceWithoutCustomFunction(t *testing.T) { func TestBackwardCompatibilityServiceWithOverride(t *testing.T) { funcCalled := false overrideCalled := false + ridInfo := "" configValue := supertokens.TypeInput{ Supertokens: &supertokens.ConnectionInfo{ ConnectionURI: "http://localhost:8080", @@ -96,6 +98,11 @@ func TestBackwardCompatibilityServiceWithOverride(t *testing.T) { EmailDelivery: &emaildelivery.TypeInput{ Override: func(originalImplementation emaildelivery.EmailDeliveryInterface) emaildelivery.EmailDeliveryInterface { (*originalImplementation.SendEmail) = func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { + u, err := url.Parse(input.EmailVerification.EmailVerifyLink) + if err != nil { + return err + } + ridInfo = u.Query().Get("rid") overrideCalled = true return nil } @@ -134,6 +141,7 @@ func TestBackwardCompatibilityServiceWithOverride(t *testing.T) { assert.Equal(t, EmailVerificationEmailSentForTest, false) assert.Equal(t, funcCalled, false) assert.Equal(t, overrideCalled, true) + assert.Equal(t, ridInfo, "") } func TestSMTPServiceOverride(t *testing.T) { diff --git a/recipe/emailverification/main.go b/recipe/emailverification/main.go index 61b68c0a..6a7d15b0 100644 --- a/recipe/emailverification/main.go +++ b/recipe/emailverification/main.go @@ -158,10 +158,7 @@ func CreateEmailVerificationLink(tenantId string, userID string, email *string, if err != nil { return evmodels.CreateEmailVerificationLinkResponse{}, err } - instance, err := getRecipeInstanceOrThrowError() - if err != nil { - return evmodels.CreateEmailVerificationLinkResponse{}, err - } + if len(userContext) == 0 { userContext = append(userContext, &map[string]interface{}{}) } @@ -176,7 +173,7 @@ func CreateEmailVerificationLink(tenantId string, userID string, email *string, }, nil } - link, err := api.GetEmailVerifyLink(st.AppInfo, emailVerificationTokenResponse.OK.Token, instance.RecipeModule.GetRecipeID(), tenantId, supertokens.GetRequestFromUserContext(userContext[0]), userContext[0]) + link, err := api.GetEmailVerifyLink(st.AppInfo, emailVerificationTokenResponse.OK.Token, tenantId, supertokens.GetRequestFromUserContext(userContext[0]), userContext[0]) if err != nil { return evmodels.CreateEmailVerificationLinkResponse{}, err diff --git a/recipe/passwordless/api/implementation.go b/recipe/passwordless/api/implementation.go index 8cf22001..4e07372b 100644 --- a/recipe/passwordless/api/implementation.go +++ b/recipe/passwordless/api/implementation.go @@ -101,7 +101,6 @@ func MakeAPIImplementation() plessmodels.APIInterface { if flowType == "MAGIC_LINK" || flowType == "USER_INPUT_CODE_AND_MAGIC_LINK" { link, err := GetMagicLink( options.AppInfo, - options.RecipeID, response.OK.PreAuthSessionID, response.OK.LinkCode, tenantId, @@ -283,7 +282,6 @@ func MakeAPIImplementation() plessmodels.APIInterface { if flowType == "MAGIC_LINK" || flowType == "USER_INPUT_CODE_AND_MAGIC_LINK" { link, err := GetMagicLink( options.AppInfo, - options.RecipeID, response.OK.PreAuthSessionID, response.OK.LinkCode, tenantId, diff --git a/recipe/passwordless/api/utils.go b/recipe/passwordless/api/utils.go index 95d002dd..77055497 100644 --- a/recipe/passwordless/api/utils.go +++ b/recipe/passwordless/api/utils.go @@ -7,16 +7,15 @@ import ( "github.com/supertokens/supertokens-golang/supertokens" ) -func GetMagicLink(appInfo supertokens.NormalisedAppinfo, recipeID string, preAuthSessionID string, linkCode string, tenantId string, request *http.Request, userContext supertokens.UserContext) (string, error) { +func GetMagicLink(appInfo supertokens.NormalisedAppinfo, preAuthSessionID string, linkCode string, tenantId string, request *http.Request, userContext supertokens.UserContext) (string, error) { websiteDomain, err := appInfo.GetOrigin(request, userContext) if err != nil { return "", err } return fmt.Sprintf( - "%s%s/verify?rid=%s&preAuthSessionId=%s&tenantId=%s#%s", + "%s%s/verify?preAuthSessionId=%s&tenantId=%s#%s", websiteDomain.GetAsStringDangerous(), appInfo.WebsiteBasePath.GetAsStringDangerous(), - recipeID, preAuthSessionID, tenantId, linkCode, diff --git a/recipe/passwordless/api_test.go b/recipe/passwordless/api_test.go index e223cc6e..f0a6c92a 100644 --- a/recipe/passwordless/api_test.go +++ b/recipe/passwordless/api_test.go @@ -31,10 +31,321 @@ import ( "github.com/supertokens/supertokens-golang/recipe/passwordless/plessmodels" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" + "github.com/supertokens/supertokens-golang/recipe/thirdparty" + "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" "github.com/supertokens/supertokens-golang/supertokens" "github.com/supertokens/supertokens-golang/test/unittesting" ) +func TestCreateCodeAPIWithRidAsThirdpartypasswordless(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + thirdparty.Init(&tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-test-id", + ClientSecret: "test-secret", + }, + }, + }, + }, + }, + }, + }), + Init(plessmodels.TypeInput{ + FlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ + Enabled: true, + }, + }), + }, + } + BeforeEach() + unittesting.SetKeyValueInConfig("passwordless_code_lifetime", "1000") + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + q, err := supertokens.GetNewQuerierInstanceOrThrowError("") + if err != nil { + t.Error(err.Error()) + } + apiV, err := q.GetQuerierAPIVersion() + if err != nil { + t.Error(err.Error()) + } + + if unittesting.MaxVersion(apiV, "2.11") == "2.11" { + return + } + + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + validEmail := map[string]interface{}{ + "email": "test@example.com", + } + + validEmailBody, err := json.Marshal(validEmail) + if err != nil { + t.Error(err.Error()) + } + + client := &http.Client{} + req, _ := http.NewRequest("POST", testServer.URL+"/auth/signinup/code", bytes.NewBuffer(validEmailBody)) + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("rid", "thirdpartypasswordless") + + validEmailResp, err := client.Do(req) + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, validEmailResp.StatusCode) + + validEmailDataInBytes, err := io.ReadAll(validEmailResp.Body) + if err != nil { + t.Error(err.Error()) + } + validEmailResp.Body.Close() + + var validEmailResult map[string]interface{} + err = json.Unmarshal(validEmailDataInBytes, &validEmailResult) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "OK", validEmailResult["status"]) + assert.Equal(t, "USER_INPUT_CODE_AND_MAGIC_LINK", validEmailResult["flowType"]) + assert.Equal(t, 4, len(validEmailResult)) +} + +func TestCreateCodeAPIWithRidAsRandom(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + thirdparty.Init(&tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-test-id", + ClientSecret: "test-secret", + }, + }, + }, + }, + }, + }, + }), + Init(plessmodels.TypeInput{ + FlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ + Enabled: true, + }, + }), + }, + } + BeforeEach() + unittesting.SetKeyValueInConfig("passwordless_code_lifetime", "1000") + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + q, err := supertokens.GetNewQuerierInstanceOrThrowError("") + if err != nil { + t.Error(err.Error()) + } + apiV, err := q.GetQuerierAPIVersion() + if err != nil { + t.Error(err.Error()) + } + + if unittesting.MaxVersion(apiV, "2.11") == "2.11" { + return + } + + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + validEmail := map[string]interface{}{ + "email": "test@example.com", + } + + validEmailBody, err := json.Marshal(validEmail) + if err != nil { + t.Error(err.Error()) + } + + client := &http.Client{} + req, _ := http.NewRequest("POST", testServer.URL+"/auth/signinup/code", bytes.NewBuffer(validEmailBody)) + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("rid", "random") + + validEmailResp, err := client.Do(req) + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, validEmailResp.StatusCode) + + validEmailDataInBytes, err := io.ReadAll(validEmailResp.Body) + if err != nil { + t.Error(err.Error()) + } + validEmailResp.Body.Close() + + var validEmailResult map[string]interface{} + err = json.Unmarshal(validEmailDataInBytes, &validEmailResult) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "OK", validEmailResult["status"]) + assert.Equal(t, "USER_INPUT_CODE_AND_MAGIC_LINK", validEmailResult["flowType"]) + assert.Equal(t, 4, len(validEmailResult)) +} + +func TestCreateCodeAPIWithWrongRid(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + thirdparty.Init(&tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-test-id", + ClientSecret: "test-secret", + }, + }, + }, + }, + }, + }, + }), + Init(plessmodels.TypeInput{ + FlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ + Enabled: true, + }, + }), + }, + } + BeforeEach() + unittesting.SetKeyValueInConfig("passwordless_code_lifetime", "1000") + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + q, err := supertokens.GetNewQuerierInstanceOrThrowError("") + if err != nil { + t.Error(err.Error()) + } + apiV, err := q.GetQuerierAPIVersion() + if err != nil { + t.Error(err.Error()) + } + + if unittesting.MaxVersion(apiV, "2.11") == "2.11" { + return + } + + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + validEmail := map[string]interface{}{ + "email": "test@example.com", + } + + validEmailBody, err := json.Marshal(validEmail) + if err != nil { + t.Error(err.Error()) + } + + client := &http.Client{} + req, _ := http.NewRequest("POST", testServer.URL+"/auth/signinup/code", bytes.NewBuffer(validEmailBody)) + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("rid", "emailpassword") + + validEmailResp, err := client.Do(req) + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, validEmailResp.StatusCode) + + validEmailDataInBytes, err := io.ReadAll(validEmailResp.Body) + if err != nil { + t.Error(err.Error()) + } + validEmailResp.Body.Close() + + var validEmailResult map[string]interface{} + err = json.Unmarshal(validEmailDataInBytes, &validEmailResult) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "OK", validEmailResult["status"]) + assert.Equal(t, "USER_INPUT_CODE_AND_MAGIC_LINK", validEmailResult["flowType"]) + assert.Equal(t, 4, len(validEmailResult)) +} + func TestWithEmailExistAPI(t *testing.T) { configValue := supertokens.TypeInput{ Supertokens: &supertokens.ConnectionInfo{ @@ -118,6 +429,89 @@ func TestWithEmailExistAPI(t *testing.T) { assert.True(t, emailExistsResponse["exists"].(bool)) } +func TestWithEmailExistAPINewPath(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + Init(plessmodels.TypeInput{ + FlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + ContactMethodEmail: plessmodels.ContactMethodEmailConfig{ + Enabled: true, + }, + }), + }, + } + + BeforeEach() + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + q, err := supertokens.GetNewQuerierInstanceOrThrowError("") + if err != nil { + t.Error(err.Error()) + } + apiV, err := q.GetQuerierAPIVersion() + if err != nil { + t.Error(err.Error()) + } + + if unittesting.MaxVersion(apiV, "2.11") == "2.11" { + return + } + + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/passwordless/email/exists", nil) + query := req.URL.Query() + query.Add("email", "test@example.com") + req.URL.RawQuery = query.Encode() + assert.NoError(t, err) + emailDoesNotExistResp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, emailDoesNotExistResp.StatusCode) + + emailDoesNotExistResponse := *unittesting.HttpResponseToConsumableInformation(emailDoesNotExistResp.Body) + + assert.Equal(t, "OK", emailDoesNotExistResponse["status"]) + assert.False(t, emailDoesNotExistResponse["exists"].(bool)) + + codeInfo, err := CreateCodeWithEmail("public", "test@example.com", nil) + assert.NoError(t, err) + + ConsumeCodeWithLinkCode("public", codeInfo.OK.LinkCode, codeInfo.OK.PreAuthSessionID) + + req, err = http.NewRequest(http.MethodGet, testServer.URL+"/auth/passwordless/email/exists", nil) + query = req.URL.Query() + query.Add("email", "test@example.com") + req.URL.RawQuery = query.Encode() + assert.NoError(t, err) + emailExistsResp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, emailExistsResp.StatusCode) + + emailExistsResponse := *unittesting.HttpResponseToConsumableInformation(emailExistsResp.Body) + + assert.Equal(t, "OK", emailExistsResponse["status"]) + assert.True(t, emailExistsResponse["exists"].(bool)) +} + func TestMagicLinkFormatInCreateCodeAPI(t *testing.T) { var magicLinkURL *url.URL sendEmail := func(input emaildelivery.EmailType, userContext supertokens.UserContext) error { @@ -195,7 +589,7 @@ func TestMagicLinkFormatInCreateCodeAPI(t *testing.T) { assert.Equal(t, "OK", validCreateCodeResponse["status"]) assert.Equal(t, "supertokens.io", magicLinkURL.Hostname()) assert.Equal(t, "/auth/verify", magicLinkURL.Path) - assert.Equal(t, "passwordless", magicLinkURL.Query().Get("rid")) + assert.Equal(t, "", magicLinkURL.Query().Get("rid")) assert.Equal(t, validCreateCodeResponse["preAuthSessionId"], magicLinkURL.Query().Get("preAuthSessionId")) } @@ -2030,6 +2424,110 @@ func TestPhoneNumberExistsAPI(t *testing.T) { assert.Equal(t, true, phoneResult1["exists"]) } +func TestPhoneNumberExistsAPINewPath(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + session.Init(&sessmodels.TypeInput{ + GetTokenTransferMethod: func(req *http.Request, forCreateNewSession bool, userContext supertokens.UserContext) sessmodels.TokenTransferMethod { + return sessmodels.CookieTransferMethod + }, + }), + Init(plessmodels.TypeInput{ + FlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + ContactMethodPhone: plessmodels.ContactMethodPhoneConfig{ + Enabled: true, + }, + }), + }, + } + BeforeEach() + unittesting.SetKeyValueInConfig("passwordless_code_lifetime", "1000") + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + if err != nil { + t.Error(err.Error()) + } + q, err := supertokens.GetNewQuerierInstanceOrThrowError("") + if err != nil { + t.Error(err.Error()) + } + apiV, err := q.GetQuerierAPIVersion() + if err != nil { + t.Error(err.Error()) + } + + if unittesting.MaxVersion(apiV, "2.11") == "2.11" { + return + } + + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + req, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/passwordless/phonenumber/exists", nil) + query := req.URL.Query() + query.Add("phoneNumber", "+1234567890") + req.URL.RawQuery = query.Encode() + assert.NoError(t, err) + phoneResp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, phoneResp.StatusCode) + + phoneDataInBytes, err := io.ReadAll(phoneResp.Body) + if err != nil { + t.Error(err.Error()) + } + phoneResp.Body.Close() + + var phoneResult map[string]interface{} + err = json.Unmarshal(phoneDataInBytes, &phoneResult) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "OK", phoneResult["status"]) + assert.Equal(t, false, phoneResult["exists"]) + + codeInfo, err := CreateCodeWithPhoneNumber("public", "+1234567890", nil) + assert.NoError(t, err) + + _, err = ConsumeCodeWithLinkCode("public", codeInfo.OK.LinkCode, codeInfo.OK.PreAuthSessionID) + assert.NoError(t, err) + + req1, err := http.NewRequest(http.MethodGet, testServer.URL+"/auth/passwordless/phonenumber/exists", nil) + query1 := req.URL.Query() + query1.Add("phoneNumber", "+1234567890") + req1.URL.RawQuery = query1.Encode() + assert.NoError(t, err) + phoneResp1, err := http.DefaultClient.Do(req1) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, phoneResp1.StatusCode) + + phoneDataInBytes1, err := io.ReadAll(phoneResp1.Body) + if err != nil { + t.Error(err.Error()) + } + phoneResp1.Body.Close() + + var phoneResult1 map[string]interface{} + err = json.Unmarshal(phoneDataInBytes1, &phoneResult1) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "OK", phoneResult1["status"]) + assert.Equal(t, true, phoneResult1["exists"]) +} + func TestResendCodeAPI(t *testing.T) { configValue := supertokens.TypeInput{ Supertokens: &supertokens.ConnectionInfo{ diff --git a/recipe/passwordless/constants.go b/recipe/passwordless/constants.go index 9e004626..9be6b7a9 100644 --- a/recipe/passwordless/constants.go +++ b/recipe/passwordless/constants.go @@ -16,9 +16,11 @@ package passwordless const ( - createCodeAPI = "/signinup/code" - resendCodeAPI = "/signinup/code/resend" - consumeCodeAPI = "/signinup/code/consume" - doesEmailExistAPI = "/signup/email/exists" - doesPhoneNumberExistAPI = "/signup/phonenumber/exists" + createCodeAPI = "/signinup/code" + resendCodeAPI = "/signinup/code/resend" + consumeCodeAPI = "/signinup/code/consume" + doesEmailExistAPIOld = "/signup/email/exists" + doesPhoneNumberExistAPIOld = "/signup/phonenumber/exists" + doesEmailExistAPI = "/passwordless/email/exists" + doesPhoneNumberExistAPI = "/passwordless/phonenumber/exists" ) diff --git a/recipe/passwordless/recipe.go b/recipe/passwordless/recipe.go index 244a80d3..5fe8cd3a 100644 --- a/recipe/passwordless/recipe.go +++ b/recipe/passwordless/recipe.go @@ -118,10 +118,18 @@ func (r *Recipe) getAPIsHandled() ([]supertokens.APIHandled, error) { if err != nil { return nil, err } + doesEmailExistsAPINormalisedOld, err := supertokens.NewNormalisedURLPath(doesEmailExistAPIOld) + if err != nil { + return nil, err + } doesEmailExistsAPINormalised, err := supertokens.NewNormalisedURLPath(doesEmailExistAPI) if err != nil { return nil, err } + doesPhoneNumberExistsAPINormalisedOld, err := supertokens.NewNormalisedURLPath(doesPhoneNumberExistAPIOld) + if err != nil { + return nil, err + } doesPhoneNumberExistsAPINormalised, err := supertokens.NewNormalisedURLPath(doesPhoneNumberExistAPI) if err != nil { return nil, err @@ -141,11 +149,21 @@ func (r *Recipe) getAPIsHandled() ([]supertokens.APIHandled, error) { PathWithoutAPIBasePath: createCodeAPINormalised, ID: createCodeAPI, Disabled: r.APIImpl.CreateCodePOST == nil, + }, { + Method: http.MethodGet, + PathWithoutAPIBasePath: doesEmailExistsAPINormalisedOld, + ID: doesEmailExistAPIOld, + Disabled: r.APIImpl.EmailExistsGET == nil, }, { Method: http.MethodGet, PathWithoutAPIBasePath: doesEmailExistsAPINormalised, ID: doesEmailExistAPI, Disabled: r.APIImpl.EmailExistsGET == nil, + }, { + Method: http.MethodGet, + PathWithoutAPIBasePath: doesPhoneNumberExistsAPINormalisedOld, + ID: doesPhoneNumberExistAPIOld, + Disabled: r.APIImpl.PhoneNumberExistsGET == nil, }, { Method: http.MethodGet, PathWithoutAPIBasePath: doesPhoneNumberExistsAPINormalised, @@ -175,9 +193,9 @@ func (r *Recipe) handleAPIRequest(id string, tenantId string, req *http.Request, return api.ConsumeCode(r.APIImpl, tenantId, options, userContext) } else if id == createCodeAPI { return api.CreateCode(r.APIImpl, tenantId, options, userContext) - } else if id == doesEmailExistAPI { + } else if id == doesEmailExistAPIOld || id == doesEmailExistAPI { return api.DoesEmailExist(r.APIImpl, tenantId, options, userContext) - } else if id == doesPhoneNumberExistAPI { + } else if id == doesPhoneNumberExistAPIOld || id == doesPhoneNumberExistAPI { return api.DoesPhoneNumberExist(r.APIImpl, tenantId, options, userContext) } else { return api.ResendCode(r.APIImpl, tenantId, options, userContext) @@ -212,7 +230,6 @@ func (r *Recipe) CreateMagicLink(email *string, phoneNumber *string, tenantId st } link, err := api.GetMagicLink( stInstance.AppInfo, - r.RecipeModule.GetRecipeID(), response.OK.PreAuthSessionID, response.OK.LinkCode, tenantId, diff --git a/recipe/passwordless/recipeFucntions_test.go b/recipe/passwordless/recipeFucntions_test.go index 99ae9f6d..acda5fb6 100644 --- a/recipe/passwordless/recipeFucntions_test.go +++ b/recipe/passwordless/recipeFucntions_test.go @@ -1044,7 +1044,7 @@ func TestCreatingMagicLink(t *testing.T) { assert.Equal(t, "supertokens.io", res.Host) assert.Equal(t, "/auth/verify", res.Path) - assert.Equal(t, "passwordless", res.Query().Get("rid")) + assert.Equal(t, "", res.Query().Get("rid")) } func TestSignInUp(t *testing.T) { diff --git a/recipe/thirdparty/authorisationUrlFeature_test.go b/recipe/thirdparty/authorisationUrlFeature_test.go index 68c06561..d2eb2f67 100644 --- a/recipe/thirdparty/authorisationUrlFeature_test.go +++ b/recipe/thirdparty/authorisationUrlFeature_test.go @@ -25,11 +25,94 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/thirdparty/tpmodels" "github.com/supertokens/supertokens-golang/supertokens" "github.com/supertokens/supertokens-golang/test/unittesting" ) +func TestReqWithThirdPartyEmailPasswordRecipe(t *testing.T) { + configValue := supertokens.TypeInput{ + Supertokens: &supertokens.ConnectionInfo{ + ConnectionURI: "http://localhost:8080", + }, + AppInfo: supertokens.AppInfo{ + APIDomain: "api.supertokens.io", + AppName: "SuperTokens", + WebsiteDomain: "supertokens.io", + }, + RecipeList: []supertokens.Recipe{ + emailpassword.Init(nil), + Init( + &tpmodels.TypeInput{ + SignInAndUpFeature: tpmodels.TypeInputSignInAndUp{ + Providers: []tpmodels.ProviderInput{ + { + Config: tpmodels.ProviderConfig{ + ThirdPartyId: "google", + Clients: []tpmodels.ProviderClientConfig{ + { + ClientID: "4398792-test-id", + ClientSecret: "test-secret", + }, + }, + }, + }, + }, + }, + }, + ), + }, + } + + BeforeEach() + unittesting.StartUpST("localhost", "8080") + defer AfterEach() + err := supertokens.Init(configValue) + + if err != nil { + t.Error(err.Error()) + } + + mux := http.NewServeMux() + testServer := httptest.NewServer(supertokens.Middleware(mux)) + defer testServer.Close() + + client := &http.Client{} + req, _ := http.NewRequest("GET", testServer.URL+"/auth/authorisationurl?thirdPartyId=google", nil) + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("rid", "thirdpartyemailpassword") + + resp, err := client.Do(req) + + if err != nil { + t.Error(err.Error()) + } + + dataInBytes, err := io.ReadAll(resp.Body) + if err != nil { + t.Error(err.Error()) + } + resp.Body.Close() + + var data map[string]interface{} + err = json.Unmarshal(dataInBytes, &data) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "OK", data["status"]) + + fetchedUrl, err := url.Parse(data["urlWithQueryParams"].(string)) + if err != nil { + t.Error(err.Error()) + } + + assert.Equal(t, "supertokens.io", fetchedUrl.Host) + assert.Equal(t, "/dev/oauth/redirect-to-provider", fetchedUrl.Path) +} + func TestUsingDevOAuthKeysWillUseDevAuthUrl(t *testing.T) { configValue := supertokens.TypeInput{ Supertokens: &supertokens.ConnectionInfo{ diff --git a/test/unittesting/testingutils.go b/test/unittesting/testingutils.go index 33f87547..f45c7d35 100644 --- a/test/unittesting/testingutils.go +++ b/test/unittesting/testingutils.go @@ -566,6 +566,42 @@ func SignInRequest(email string, password string, testUrl string) (*http.Respons return resp, nil } +func SignInRequestWithThirdpartyemailpasswordRid(email string, password string, testUrl string) (*http.Response, error) { + formFields := map[string][]map[string]string{ + "formFields": { + { + "id": "email", + "value": email, + }, + { + "id": "password", + "value": password, + }, + }, + } + + postBody, err := json.Marshal(formFields) + if err != nil { + fmt.Println(err.Error()) + return nil, err + } + + client := &http.Client{} + req, _ := http.NewRequest("POST", testUrl+"/auth/signin", bytes.NewBuffer(postBody)) + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("rid", "thirdpartyemailpassword") + + resp, err := client.Do(req) + + if err != nil { + fmt.Println(err.Error()) + return nil, err + } + + return resp, nil +} + func SignInRequestWithTenantId(tenantId string, email string, password string, testUrl string) (*http.Response, error) { formFields := map[string][]map[string]string{ "formFields": {