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
|
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) {
|
func (s *Server) verify(w http.ResponseWriter, req *http.Request) {
|
||||||
if !getGetData(w, req) {
|
if !getGetData(w, req) {
|
||||||
return
|
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) {
|
func TestServerVerifyAccountSuccess(t *testing.T) {
|
||||||
testStore := TestStore{}
|
testStore := TestStore{}
|
||||||
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
s := Server{&TestAuth{}, &testStore, &TestEnv{}, &TestMail{}}
|
||||||
|
@ -402,7 +543,10 @@ func TestServerVerifyAccountErrors(t *testing.T) {
|
||||||
expectStatusCode(t, w, tc.expectedStatusCode)
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
||||||
expectErrorString(t, body, tc.expectedErrorString)
|
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")
|
t.Errorf("Expected Store.VerifyAccount not to be called")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,6 +23,7 @@ const PathPrometheus = "/metrics"
|
||||||
const PathAuthToken = PathPrefix + "/auth/full"
|
const PathAuthToken = PathPrefix + "/auth/full"
|
||||||
const PathRegister = PathPrefix + "/signup"
|
const PathRegister = PathPrefix + "/signup"
|
||||||
const PathVerify = PathPrefix + "/verify"
|
const PathVerify = PathPrefix + "/verify"
|
||||||
|
const PathResendVerify = PathPrefix + "/verify/resend"
|
||||||
const PathPassword = PathPrefix + "/password"
|
const PathPassword = PathPrefix + "/password"
|
||||||
const PathWallet = PathPrefix + "/wallet"
|
const PathWallet = PathPrefix + "/wallet"
|
||||||
const PathClientSaltSeed = PathPrefix + "/client-salt-seed"
|
const PathClientSaltSeed = PathPrefix + "/client-salt-seed"
|
||||||
|
|
|
@ -96,6 +96,7 @@ type TestStoreFunctionsCalled struct {
|
||||||
GetToken auth.AuthTokenString
|
GetToken auth.AuthTokenString
|
||||||
GetUserId bool
|
GetUserId bool
|
||||||
CreateAccount *CreateAccountCall
|
CreateAccount *CreateAccountCall
|
||||||
|
UpdateVerifyTokenString bool
|
||||||
VerifyAccount bool
|
VerifyAccount bool
|
||||||
SetWallet SetWalletCall
|
SetWallet SetWalletCall
|
||||||
GetWallet bool
|
GetWallet bool
|
||||||
|
@ -109,6 +110,7 @@ type TestStoreFunctionsErrors struct {
|
||||||
GetToken error
|
GetToken error
|
||||||
GetUserId error
|
GetUserId error
|
||||||
CreateAccount error
|
CreateAccount error
|
||||||
|
UpdateVerifyTokenString error
|
||||||
VerifyAccount error
|
VerifyAccount error
|
||||||
SetWallet error
|
SetWallet error
|
||||||
GetWallet error
|
GetWallet error
|
||||||
|
@ -159,6 +161,11 @@ func (s *TestStore) CreateAccount(email auth.Email, password auth.Password, seed
|
||||||
return s.Errors.CreateAccount
|
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) {
|
func (s *TestStore) VerifyAccount(auth.VerifyTokenString) (err error) {
|
||||||
s.Called.VerifyAccount = true
|
s.Called.VerifyAccount = true
|
||||||
return s.Errors.VerifyAccount
|
return s.Errors.VerifyAccount
|
||||||
|
|
|
@ -42,6 +42,7 @@ type StoreInterface interface {
|
||||||
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
||||||
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
||||||
CreateAccount(auth.Email, auth.Password, auth.ClientSaltSeed, auth.VerifyTokenString) error
|
CreateAccount(auth.Email, auth.Password, auth.ClientSaltSeed, auth.VerifyTokenString) error
|
||||||
|
UpdateVerifyTokenString(auth.Email, auth.VerifyTokenString) error
|
||||||
VerifyAccount(auth.VerifyTokenString) error
|
VerifyAccount(auth.VerifyTokenString) error
|
||||||
ChangePasswordWithWallet(auth.Email, auth.Password, auth.Password, auth.ClientSaltSeed, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) 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
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateVerifyTokenString(auth.Email, auth.VerifyTokenString) (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) VerifyAccount(auth.VerifyTokenString) (err error) {
|
func (s *Store) VerifyAccount(auth.VerifyTokenString) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue