diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go index d263629a5..3ad399ebc 100644 --- a/server/resolvers/verify_email.go +++ b/server/resolvers/verify_email.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" log "github.com/sirupsen/logrus" + "github.com/authorizerdev/authorizer/server/authenticators" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/db" @@ -60,6 +61,66 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m return res, err } + isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication) + if err != nil || !isMFADisabled { + log.Debug("MFA service not enabled: ", err) + } + + isTOTPLoginDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableTOTPLogin) + if err != nil || !isTOTPLoginDisabled { + log.Debug("totp service not enabled: ", err) + } + + setOTPMFaSession := func(expiresAt int64) error { + mfaSession := uuid.NewString() + err = memorystore.Provider.SetMfaSession(user.ID, mfaSession, expiresAt) + if err != nil { + log.Debug("Failed to add mfasession: ", err) + return err + } + cookie.SetMfaSession(gc, mfaSession) + return nil + } + + // If mfa enabled and also totp enabled + if refs.BoolValue(user.IsMultiFactorAuthEnabled) && !isMFADisabled && !isTOTPLoginDisabled { + expiresAt := time.Now().Add(3 * time.Minute).Unix() + if err := setOTPMFaSession(expiresAt); err != nil { + log.Debug("Failed to set mfa session: ", err) + return nil, err + } + authenticator, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, user.ID, constants.EnvKeyTOTPAuthenticator) + if err != nil || authenticator == nil || authenticator.VerifiedAt == nil { + // generate totp + // Generate a base64 URL and initiate the registration for TOTP + authConfig, err := authenticators.Provider.Generate(ctx, user.ID) + if err != nil { + log.Debug("error while generating base64 url: ", err) + return nil, err + } + recoveryCodes := []*string{} + for _, code := range authConfig.RecoveryCodes { + recoveryCodes = append(recoveryCodes, refs.NewStringRef(code)) + } + // when user is first time registering for totp + res = &model.AuthResponse{ + Message: `Proceed to totp verification screen`, + ShouldShowTotpScreen: refs.NewBoolRef(true), + AuthenticatorScannerImage: refs.NewStringRef(authConfig.ScannerImage), + AuthenticatorSecret: refs.NewStringRef(authConfig.Secret), + AuthenticatorRecoveryCodes: recoveryCodes, + } + return res, nil + } else { + //when user is already register for totp + res = &model.AuthResponse{ + Message: `Proceed to totp screen`, + ShouldShowTotpScreen: refs.NewBoolRef(true), + } + return res, nil + } + } + isSignUp := false if user.EmailVerifiedAt == nil { isSignUp = true diff --git a/server/test/integration_test.go b/server/test/integration_test.go index 07549ec5f..5329e993d 100644 --- a/server/test/integration_test.go +++ b/server/test/integration_test.go @@ -130,6 +130,7 @@ func TestResolvers(t *testing.T) { mobileSingupTest(t, s) mobileLoginTests(t, s) totpLoginTest(t, s) + totpSignupTest(t, s) forgotPasswordTest(t, s) forgotPasswordMobileTest(t, s) resendVerifyEmailTests(t, s) diff --git a/server/test/signup_test.go b/server/test/signup_test.go index 47065737b..ec8cdb36b 100644 --- a/server/test/signup_test.go +++ b/server/test/signup_test.go @@ -37,7 +37,7 @@ func signupTests(t *testing.T, s TestSetup) { Password: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password, }) - assert.NotNil(t, err, "singup disabled") + assert.NotNil(t, err, "signup disabled") assert.Nil(t, res) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false) res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ diff --git a/server/test/totp_login_test.go b/server/test/totp_login_test.go index 11b992fd4..8eef7953c 100644 --- a/server/test/totp_login_test.go +++ b/server/test/totp_login_test.go @@ -92,6 +92,7 @@ func totpLoginTest(t *testing.T, s TestSetup) { assert.NotNil(t, tf) code := tf.OTP() assert.NotEmpty(t, code) + // Set mfa cookie session mfaSession := uuid.NewString() memorystore.Provider.SetMfaSession(verifyRes.User.ID, mfaSession, time.Now().Add(1*time.Minute).Unix()) @@ -122,6 +123,7 @@ func totpLoginTest(t *testing.T, s TestSetup) { cookie = fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken) cookie = strings.TrimSuffix(cookie, ";") req.Header.Set("Cookie", cookie) + //logged out logout, err := resolvers.LogoutResolver(ctx) assert.NoError(t, err) diff --git a/server/test/totp_signup_test.go b/server/test/totp_signup_test.go new file mode 100644 index 000000000..6dc5a0d3c --- /dev/null +++ b/server/test/totp_signup_test.go @@ -0,0 +1,187 @@ +package test + +import ( + "bytes" + "encoding/base64" + "fmt" + "strings" + "testing" + "time" + + "github.com/authorizerdev/authorizer/server/authenticators" + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/token" + "github.com/gokyle/twofactor" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/tuotoo/qrcode" +) + +func totpSignupTest(t *testing.T, s TestSetup) { + t.Helper() + // Test case to verify TOTP for signup + t.Run(`should verify totp for signup`, func(t *testing.T) { + // Create request and context using test setup + req, ctx := createContext(s) + email := "verify_totp." + s.TestInfo.Email + + // Test case: Invalid password (confirm password mismatch) + res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password + "s", + }) + assert.NotNil(t, err, "invalid password") + assert.Nil(t, res) + + { + // Test case: Invalid password ("test" as the password) + res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: "test", + ConfirmPassword: "test", + }) + assert.NotNil(t, err, "invalid password") + assert.Nil(t, res) + } + + { + // Test case: Signup disabled + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true) + res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password, + }) + assert.NotNil(t, err, "signup disabled") + assert.Nil(t, res) + } + + { + // Test case: Successful signup + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false) + res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password, + AppData: map[string]interface{}{ + "test": "test", + }, + }) + assert.Nil(t, err, "signup should be successful") + user := *res.User + assert.Equal(t, email, refs.StringValue(user.Email)) + assert.Equal(t, "test", user.AppData["test"]) + assert.Nil(t, res.AccessToken, "access token should be nil") + } + + { + // Test case: Duplicate email (should throw an error) + res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password, + }) + assert.NotNil(t, err, "should throw duplicate email error") + assert.Nil(t, res) + } + + // Clean up data for the email + cleanData(email) + + { + // Test case: Email verification and TOTP setup + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableEmailVerification, false) + + // Sign up a user + res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password, + }) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.Equal(t, "Verification email has been sent. Please check your inbox", res.Message) + + // Retrieve user and update for TOTP setup + user, err := db.Provider.GetUserByID(ctx, res.User.ID) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.NotNil(t, user) + + // Enable multi-factor authentication and update the user + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, false) + user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true) + updatedUser, err := db.Provider.UpdateUser(ctx, user) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.Equal(t, true, *updatedUser.IsMultiFactorAuthEnabled) + + // Initialise totp authenticator store + authenticators.InitTOTPStore() + + // Verify an email and get TOTP response + verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) + assert.Nil(t, err) + assert.Equal(t, email, verificationRequest.Email) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ + Token: verificationRequest.Token, + }) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.NotNil(t, &verifyRes) + assert.Nil(t, verifyRes.AccessToken) + assert.Equal(t, "Proceed to totp verification screen", verifyRes.Message) + assert.NotEqual(t, *verifyRes.AuthenticatorScannerImage, "", "totp url should not be empty") + assert.NotEqual(t, *verifyRes.AuthenticatorSecret, "", "totp secret should not be empty") + assert.NotNil(t, verifyRes.AuthenticatorRecoveryCodes) + + // Get TOTP URL for for validation + pngBytes, err := base64.StdEncoding.DecodeString(*verifyRes.AuthenticatorScannerImage) + assert.NoError(t, err) + qrmatrix, err := qrcode.Decode(bytes.NewReader(pngBytes)) + assert.NoError(t, err) + tf, label, err := twofactor.FromURL(qrmatrix.Content) + data := strings.Split(label, ":") + assert.NoError(t, err) + assert.Equal(t, email, data[1]) + assert.NotNil(t, tf) + code := tf.OTP() + assert.NotEmpty(t, code) + + // Set MFA cookie session + mfaSession := uuid.NewString() + memorystore.Provider.SetMfaSession(res.User.ID, mfaSession, time.Now().Add(1*time.Minute).Unix()) + cookie := fmt.Sprintf("%s=%s;", constants.MfaCookieName+"_session", mfaSession) + cookie = strings.TrimSuffix(cookie, ";") + req.Header.Set("Cookie", cookie) + valid, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ + Email: &email, + IsTotp: refs.NewBoolRef(true), + Otp: code, + }) + accessToken := *valid.AccessToken + assert.NoError(t, err) + assert.NotNil(t, accessToken) + assert.NotEmpty(t, valid.Message) + assert.NotEmpty(t, accessToken) + claims, err := token.ParseJWTToken(accessToken) + assert.NoError(t, err) + assert.NotEmpty(t, claims) + signUpMethod := claims["login_method"] + sessionKey := res.User.ID + if signUpMethod != nil && signUpMethod != "" { + sessionKey = signUpMethod.(string) + ":" + res.User.ID + } + sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string)) + assert.NoError(t, err) + assert.NotEmpty(t, sessionToken) + cookie = fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken) + cookie = strings.TrimSuffix(cookie, ";") + req.Header.Set("Cookie", cookie) + } + // Clean up data for the email + cleanData(email) + }) +} diff --git a/server/test/verify_otp_test.go b/server/test/verify_otp_test.go index c01968d86..c96593272 100644 --- a/server/test/verify_otp_test.go +++ b/server/test/verify_otp_test.go @@ -1,92 +1,202 @@ package test import ( + "bytes" "context" + "encoding/base64" "fmt" "strings" "testing" "time" + "github.com/gokyle/twofactor" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/tuotoo/qrcode" + + "github.com/authorizerdev/authorizer/server/authenticators" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/refs" "github.com/authorizerdev/authorizer/server/resolvers" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" + "github.com/authorizerdev/authorizer/server/token" ) func verifyOTPTest(t *testing.T, s TestSetup) { t.Helper() t.Run(`should verify otp`, func(t *testing.T) { + // Set up request and context using test setup req, ctx := createContext(s) email := "verify_otp." + s.TestInfo.Email - res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ - Email: refs.NewStringRef(email), - Password: s.TestInfo.Password, - ConfirmPassword: s.TestInfo.Password, - }) - assert.NoError(t, err) - assert.NotNil(t, res) - - // Login should fail as email is not verified - loginRes, err := resolvers.LoginResolver(ctx, model.LoginInput{ - Email: refs.NewStringRef(email), - Password: s.TestInfo.Password, - }) - assert.Error(t, err) - assert.Nil(t, loginRes) - verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) - assert.Nil(t, err) - assert.Equal(t, email, verificationRequest.Email) - verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ - Token: verificationRequest.Token, - }) - assert.Nil(t, err) - assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty") - - // Using access token update profile - s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken)) - ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) - memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, false) - memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, true) - memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisablePhoneVerification, true) - updateProfileRes, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ - IsMultiFactorAuthEnabled: refs.NewBoolRef(true), - }) - assert.NoError(t, err) - assert.NotEmpty(t, updateProfileRes.Message) - - // Login should not return error but access token should be empty as otp should have been sent - loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{ - Email: refs.NewStringRef(email), - Password: s.TestInfo.Password, - }) - assert.NoError(t, err) - assert.NotNil(t, loginRes) - assert.Nil(t, loginRes.AccessToken) - - // Get otp from db - otp, err := db.Provider.GetOTPByEmail(ctx, email) - assert.NoError(t, err) - assert.NotEmpty(t, otp.Otp) - // Get user by email - user, err := db.Provider.GetUserByEmail(ctx, email) - assert.NoError(t, err) - assert.NotNil(t, user) - // Set mfa cookie session - mfaSession := uuid.NewString() - memorystore.Provider.SetMfaSession(user.ID, mfaSession, time.Now().Add(1*time.Minute).Unix()) - cookie := fmt.Sprintf("%s=%s;", constants.MfaCookieName+"_session", mfaSession) - cookie = strings.TrimSuffix(cookie, ";") - req.Header.Set("Cookie", cookie) - verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ - Email: &email, - Otp: otp.Otp, - }) - assert.Nil(t, err) - assert.NotEqual(t, verifyOtpRes.AccessToken, "", "access token should not be empty") - cleanData(email) + + // Test case: Setup email OTP MFA for login + { + // Sign up a user + res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password, + }) + assert.NoError(t, err) + assert.NotNil(t, res) + + // Attempt to login should fail as email is not verified + loginRes, err := resolvers.LoginResolver(ctx, model.LoginInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + }) + assert.NotNil(t, err, "email is not verified") + assert.Nil(t, loginRes) + + // Verify the email + verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) + assert.Nil(t, err) + assert.Equal(t, email, verificationRequest.Email) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ + Token: verificationRequest.Token, + }) + assert.Nil(t, err) + assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty") + + // Use access token to update the profile + s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken)) + ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMailOTPLogin, false) + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, true) + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisablePhoneVerification, true) + updateProfileRes, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ + IsMultiFactorAuthEnabled: refs.NewBoolRef(true), + }) + assert.NoError(t, err) + assert.NotEmpty(t, updateProfileRes.Message) + + // Login should not return an error, but the access token should be empty as OTP should have been sent + loginRes, err = resolvers.LoginResolver(ctx, model.LoginInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + }) + assert.NoError(t, err) + assert.NotNil(t, loginRes) + assert.Nil(t, loginRes.AccessToken) + + // Get OTP from db + otp, err := db.Provider.GetOTPByEmail(ctx, email) + assert.NoError(t, err) + assert.NotEmpty(t, otp.Otp) + + // Get user by email + user, err := db.Provider.GetUserByEmail(ctx, email) + assert.NoError(t, err) + assert.NotNil(t, user) + + // Set MFA cookie session + mfaSession := uuid.NewString() + memorystore.Provider.SetMfaSession(user.ID, mfaSession, time.Now().Add(1*time.Minute).Unix()) + cookie := fmt.Sprintf("%s=%s;", constants.MfaCookieName+"_session", mfaSession) + cookie = strings.TrimSuffix(cookie, ";") + req.Header.Set("Cookie", cookie) + + // Verify OTP + verifyOtpRes, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ + Email: &email, + Otp: otp.Otp, + }) + assert.Nil(t, err) + assert.NotEqual(t, verifyOtpRes.AccessToken, "", "access token should not be empty") + + // Clean up data for the email + cleanData(email) + } + + // Test case: Setup TOTP MFA for signup + { + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableEmailVerification, false) + signUpRes, err := resolvers.SignupResolver(ctx, model.SignUpInput{ + Email: refs.NewStringRef(email), + Password: s.TestInfo.Password, + ConfirmPassword: s.TestInfo.Password, + }) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.Equal(t, "Verification email has been sent. Please check your inbox", signUpRes.Message) + + // Retrieve user and update for TOTP setup + user, err := db.Provider.GetUserByID(ctx, signUpRes.User.ID) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.NotNil(t, user) + + // Enable multi-factor authentication and update the user + memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableTOTPLogin, false) + user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true) + updatedUser, err := db.Provider.UpdateUser(ctx, user) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.Equal(t, true, *updatedUser.IsMultiFactorAuthEnabled) + + // Initialise totp authenticator store + authenticators.InitTOTPStore() + + // Verify an email and get TOTP response + verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) + assert.Nil(t, err) + assert.Equal(t, email, verificationRequest.Email) + verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ + Token: verificationRequest.Token, + }) + assert.Nil(t, err, "Expected no error but got: %v", err) + assert.NotNil(t, &verifyRes) + assert.Nil(t, verifyRes.AccessToken) + assert.Equal(t, "Proceed to totp verification screen", verifyRes.Message) + assert.NotEqual(t, *verifyRes.AuthenticatorScannerImage, "", "totp url should not be empty") + assert.NotEqual(t, *verifyRes.AuthenticatorSecret, "", "totp secret should not be empty") + assert.NotNil(t, verifyRes.AuthenticatorRecoveryCodes) + + // Get TOTP URL for validation + pngBytes, err := base64.StdEncoding.DecodeString(*verifyRes.AuthenticatorScannerImage) + assert.NoError(t, err) + qrmatrix, err := qrcode.Decode(bytes.NewReader(pngBytes)) + assert.NoError(t, err) + tf, label, err := twofactor.FromURL(qrmatrix.Content) + data := strings.Split(label, ":") + assert.NoError(t, err) + assert.Equal(t, email, data[1]) + assert.NotNil(t, tf) + code := tf.OTP() + assert.NotEmpty(t, code) + + // Set mfa cookie session + mfaSession := uuid.NewString() + memorystore.Provider.SetMfaSession(signUpRes.User.ID, mfaSession, time.Now().Add(1*time.Minute).Unix()) + cookie := fmt.Sprintf("%s=%s;", constants.MfaCookieName+"_session", mfaSession) + cookie = strings.TrimSuffix(cookie, ";") + req.Header.Set("Cookie", cookie) + valid, err := resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ + Email: &email, + IsTotp: refs.NewBoolRef(true), + Otp: code, + }) + accessToken := *valid.AccessToken + assert.NoError(t, err) + assert.NotNil(t, accessToken) + assert.NotEmpty(t, valid.Message) + assert.NotEmpty(t, accessToken) + claims, err := token.ParseJWTToken(accessToken) + assert.NoError(t, err) + assert.NotEmpty(t, claims) + signUpMethod := claims["login_method"] + sessionKey := signUpRes.User.ID + if signUpMethod != nil && signUpMethod != "" { + sessionKey = signUpMethod.(string) + ":" + signUpRes.User.ID + } + sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string)) + assert.NoError(t, err) + assert.NotEmpty(t, sessionToken) + cookie = fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken) + cookie = strings.TrimSuffix(cookie, ";") + req.Header.Set("Cookie", cookie) + + // Clean up data for the email + cleanData(email) + } }) }