Endpoint to re-send verify token string
This commit is contained in:
parent
c8620f7c8c
commit
5ffcddf8f7
5 changed files with 220 additions and 1 deletions
|
@ -138,6 +138,68 @@ func getVerifyParams(req *http.Request) (token auth.VerifyTokenString, err error
|
|||
return
|
||||
}
|
||||
|
||||
type ResendVerifyEmailRequest struct {
|
||||
Email auth.Email `json:"email"`
|
||||
}
|
||||
|
||||
func (r *ResendVerifyEmailRequest) validate() error {
|
||||
if !r.Email.Validate() {
|
||||
return fmt.Errorf("Invalid or missing 'email'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) resendVerifyEmail(w http.ResponseWriter, req *http.Request) {
|
||||
verificationMode, err := env.GetAccountVerificationMode(s.env)
|
||||
if err != nil {
|
||||
internalServiceErrorJson(w, err, "Error getting account verification mode")
|
||||
return
|
||||
}
|
||||
if verificationMode != env.AccountVerificationModeEmailVerify {
|
||||
errorJson(w, http.StatusForbidden, "Account verification mode is not set to EmailVerify")
|
||||
return
|
||||
}
|
||||
|
||||
var resendVerifyEmailRequest ResendVerifyEmailRequest
|
||||
if !getPostData(w, req, &resendVerifyEmailRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
token, err := s.auth.NewVerifyTokenString()
|
||||
|
||||
if err != nil {
|
||||
internalServiceErrorJson(w, err, "Error generating verify token string")
|
||||
return
|
||||
}
|
||||
|
||||
err = s.store.UpdateVerifyTokenString(resendVerifyEmailRequest.Email, token)
|
||||
if err == store.ErrWrongCredentials {
|
||||
errorJson(w, http.StatusUnauthorized, "No match for email")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
internalServiceErrorJson(w, err, "Error updating verify token string")
|
||||
return
|
||||
}
|
||||
|
||||
err = s.mail.SendVerificationEmail(resendVerifyEmailRequest.Email, token)
|
||||
|
||||
if err != nil {
|
||||
internalServiceErrorJson(w, err, "Error re-sending verification email")
|
||||
return
|
||||
}
|
||||
|
||||
var verifyResponse struct{}
|
||||
response, err := json.Marshal(verifyResponse)
|
||||
|
||||
if err != nil {
|
||||
internalServiceErrorJson(w, err, "Error generating verify response")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, string(response))
|
||||
}
|
||||
|
||||
func (s *Server) verify(w http.ResponseWriter, req *http.Request) {
|
||||
if !getGetData(w, req) {
|
||||
return
|
||||
|
|
|
@ -324,6 +324,147 @@ func TestServerValidateRegisterRequest(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServerResendVerifyEmailSuccess(t *testing.T) {
|
||||
testStore := TestStore{}
|
||||
testMail := TestMail{}
|
||||
|
||||
env := map[string]string{
|
||||
"ACCOUNT_VERIFICATION_MODE": "EmailVerify",
|
||||
}
|
||||
s := Server{&TestAuth{}, &testStore, &TestEnv{env}, &testMail}
|
||||
|
||||
requestBody := []byte(`{"email": "abc@example.com"}`)
|
||||
req := httptest.NewRequest(http.MethodPost, PathVerify, bytes.NewBuffer(requestBody))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.resendVerifyEmail(w, req)
|
||||
body, _ := ioutil.ReadAll(w.Body)
|
||||
|
||||
expectStatusCode(t, w, http.StatusOK)
|
||||
|
||||
if string(body) != "{}" {
|
||||
t.Errorf("Expected register response to be \"{}\": result: %+v", string(body))
|
||||
}
|
||||
|
||||
if !testStore.Called.UpdateVerifyTokenString {
|
||||
t.Errorf("Expected Store.UpdateVerifyTokenString to be called")
|
||||
}
|
||||
|
||||
if testMail.SendVerificationEmailCall == nil {
|
||||
// We're doing EmailVerify for this test.
|
||||
t.Fatalf("Expected Store.SendVerificationEmail to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerResendVerifyEmailErrors(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
omitEmailAddress bool
|
||||
accountVerificationMode string
|
||||
|
||||
expectedStatusCode int
|
||||
expectedErrorString string
|
||||
expectedCallSendVerificationEmail bool
|
||||
expectedCallUpdateVerifyTokenString bool
|
||||
|
||||
storeErrors TestStoreFunctionsErrors
|
||||
mailError error
|
||||
}{
|
||||
|
||||
{
|
||||
name: "wrong account verification mode",
|
||||
accountVerificationMode: "Whitelist",
|
||||
expectedStatusCode: http.StatusForbidden,
|
||||
expectedErrorString: http.StatusText(http.StatusForbidden) + ": Account verification mode is not set to EmailVerify",
|
||||
expectedCallSendVerificationEmail: false,
|
||||
expectedCallUpdateVerifyTokenString: false,
|
||||
},
|
||||
{
|
||||
name: "validation error",
|
||||
accountVerificationMode: "EmailVerify",
|
||||
omitEmailAddress: true,
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation: Invalid or missing 'email'",
|
||||
expectedCallSendVerificationEmail: false,
|
||||
expectedCallUpdateVerifyTokenString: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "not found email",
|
||||
accountVerificationMode: "EmailVerify",
|
||||
expectedStatusCode: http.StatusUnauthorized,
|
||||
expectedErrorString: http.StatusText(http.StatusUnauthorized) + ": No match for email",
|
||||
storeErrors: TestStoreFunctionsErrors{UpdateVerifyTokenString: store.ErrWrongCredentials},
|
||||
expectedCallSendVerificationEmail: false,
|
||||
expectedCallUpdateVerifyTokenString: true,
|
||||
},
|
||||
{
|
||||
name: "assorted db error",
|
||||
accountVerificationMode: "EmailVerify",
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||
storeErrors: TestStoreFunctionsErrors{UpdateVerifyTokenString: fmt.Errorf("TestStore.UpdateVerifyTokenString fail")},
|
||||
expectedCallSendVerificationEmail: false,
|
||||
expectedCallUpdateVerifyTokenString: true,
|
||||
},
|
||||
{
|
||||
name: "fail to generate verification email",
|
||||
accountVerificationMode: "EmailVerify",
|
||||
expectedStatusCode: http.StatusInternalServerError,
|
||||
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||
expectedCallSendVerificationEmail: true,
|
||||
expectedCallUpdateVerifyTokenString: true,
|
||||
|
||||
mailError: fmt.Errorf("TestEmail.SendVerificationEmail fail"),
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
env := map[string]string{
|
||||
"ACCOUNT_VERIFICATION_MODE": tc.accountVerificationMode,
|
||||
}
|
||||
|
||||
// Set this up to fail according to specification
|
||||
testStore := TestStore{Errors: tc.storeErrors}
|
||||
testMail := TestMail{SendVerificationEmailError: tc.mailError}
|
||||
s := Server{&TestAuth{}, &testStore, &TestEnv{env}, &testMail}
|
||||
|
||||
// Make request
|
||||
var requestBody []byte
|
||||
if tc.omitEmailAddress {
|
||||
requestBody = []byte(`{}`)
|
||||
} else {
|
||||
requestBody = []byte(`{"email": "abc@example.com"}`)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, PathVerify, bytes.NewBuffer(requestBody))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.resendVerifyEmail(w, req)
|
||||
body, _ := ioutil.ReadAll(w.Body)
|
||||
|
||||
expectStatusCode(t, w, tc.expectedStatusCode)
|
||||
expectErrorString(t, body, tc.expectedErrorString)
|
||||
|
||||
if tc.expectedCallUpdateVerifyTokenString && !testStore.Called.UpdateVerifyTokenString {
|
||||
t.Errorf("Expected Store.UpdateVerifyTokenString to be called")
|
||||
}
|
||||
if !tc.expectedCallUpdateVerifyTokenString && testStore.Called.UpdateVerifyTokenString {
|
||||
t.Errorf("Expected Store.UpdateVerifyTokenString not to be called")
|
||||
}
|
||||
|
||||
if tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall == nil {
|
||||
// We're doing EmailVerify for this test.
|
||||
t.Fatalf("Expected Store.SendVerificationEmail to be called")
|
||||
}
|
||||
if !tc.expectedCallSendVerificationEmail && testMail.SendVerificationEmailCall != nil {
|
||||
// We're doing EmailVerify for this test.
|
||||
t.Fatalf("Expected Store.SendVerificationEmail not to be called")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerVerifyAccountSuccess(t *testing.T) {
|
||||
testStore := TestStore{}
|
||||
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
||||
|
@ -402,7 +543,10 @@ func TestServerVerifyAccountErrors(t *testing.T) {
|
|||
expectStatusCode(t, w, tc.expectedStatusCode)
|
||||
expectErrorString(t, body, tc.expectedErrorString)
|
||||
|
||||
if tc.expectedCallVerifyAccount != testStore.Called.VerifyAccount {
|
||||
if tc.expectedCallVerifyAccount && !testStore.Called.VerifyAccount {
|
||||
t.Errorf("Expected Store.VerifyAccount to be called")
|
||||
}
|
||||
if !tc.expectedCallVerifyAccount && testStore.Called.VerifyAccount {
|
||||
t.Errorf("Expected Store.VerifyAccount not to be called")
|
||||
}
|
||||
})
|
||||
|
|
|
@ -23,6 +23,7 @@ const PathPrometheus = "/metrics"
|
|||
const PathAuthToken = PathPrefix + "/auth/full"
|
||||
const PathRegister = PathPrefix + "/signup"
|
||||
const PathVerify = PathPrefix + "/verify"
|
||||
const PathResendVerify = PathPrefix + "/verify/resend"
|
||||
const PathPassword = PathPrefix + "/password"
|
||||
const PathWallet = PathPrefix + "/wallet"
|
||||
const PathClientSaltSeed = PathPrefix + "/client-salt-seed"
|
||||
|
|
|
@ -96,6 +96,7 @@ type TestStoreFunctionsCalled struct {
|
|||
GetToken auth.AuthTokenString
|
||||
GetUserId bool
|
||||
CreateAccount *CreateAccountCall
|
||||
UpdateVerifyTokenString bool
|
||||
VerifyAccount bool
|
||||
SetWallet SetWalletCall
|
||||
GetWallet bool
|
||||
|
@ -109,6 +110,7 @@ type TestStoreFunctionsErrors struct {
|
|||
GetToken error
|
||||
GetUserId error
|
||||
CreateAccount error
|
||||
UpdateVerifyTokenString error
|
||||
VerifyAccount error
|
||||
SetWallet error
|
||||
GetWallet error
|
||||
|
@ -159,6 +161,11 @@ func (s *TestStore) CreateAccount(email auth.Email, password auth.Password, seed
|
|||
return s.Errors.CreateAccount
|
||||
}
|
||||
|
||||
func (s *TestStore) UpdateVerifyTokenString(auth.Email, auth.VerifyTokenString) (err error) {
|
||||
s.Called.UpdateVerifyTokenString = true
|
||||
return s.Errors.UpdateVerifyTokenString
|
||||
}
|
||||
|
||||
func (s *TestStore) VerifyAccount(auth.VerifyTokenString) (err error) {
|
||||
s.Called.VerifyAccount = true
|
||||
return s.Errors.VerifyAccount
|
||||
|
|
|
@ -42,6 +42,7 @@ type StoreInterface interface {
|
|||
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
||||
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
||||
CreateAccount(auth.Email, auth.Password, auth.ClientSaltSeed, auth.VerifyTokenString) error
|
||||
UpdateVerifyTokenString(auth.Email, auth.VerifyTokenString) error
|
||||
VerifyAccount(auth.VerifyTokenString) error
|
||||
ChangePasswordWithWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) error
|
||||
ChangePasswordNoWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed) error
|
||||
|
@ -383,6 +384,10 @@ func (s *Store) CreateAccount(email auth.Email, password auth.Password, seed aut
|
|||
return
|
||||
}
|
||||
|
||||
func (s *Store) UpdateVerifyTokenString(auth.Email, auth.VerifyTokenString) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Store) VerifyAccount(auth.VerifyTokenString) (err error) {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue