From 0a86ba6136e58ea8e2bd0958d81958a201eef058 Mon Sep 17 00:00:00 2001 From: Erica Date: Mon, 4 Dec 2023 11:15:28 -0800 Subject: [PATCH 1/9] wip --- internal/data/receiver_verification.go | 37 +++++++++++-- internal/data/receiver_verification_test.go | 54 +++++++++++++++++++ .../htmltemplate/tmpl/receiver_register.tmpl | 29 +++++++++- .../httphandler/receiver_registration.go | 26 ++++++--- 4 files changed, 134 insertions(+), 12 deletions(-) diff --git a/internal/data/receiver_verification.go b/internal/data/receiver_verification.go index d85a25502..af87670cf 100644 --- a/internal/data/receiver_verification.go +++ b/internal/data/receiver_verification.go @@ -30,6 +30,7 @@ type ReceiverVerificationInsert struct { ReceiverID string `db:"receiver_id"` VerificationField VerificationField `db:"verification_field"` VerificationValue string `db:"hashed_value"` + UpdatedAt *time.Time `db:"updated_at"` } const MaxAttemptsAllowed = 15 @@ -91,6 +92,30 @@ func (m ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlEx return receiverVerifications, nil } +func (m *ReceiverVerificationModel) GetLatestByReceiverId(ctx context.Context, sqlExec db.SQLExecuter, receiverId string) (*ReceiverVerification, error) { + receiverVerifications := []ReceiverVerification{} + query := ` + SELECT + * + FROM + receiver_verifications + WHERE + receiver_id = $1 + ORDER BY + updated_at DESC + ` + + err := sqlExec.SelectContext(ctx, &receiverVerifications, query, receiverId) + if err != nil { + return nil, fmt.Errorf("error querying receiver verifications: %w", err) + } + if len(receiverVerifications) == 0 { + return nil, fmt.Errorf("cannot query any receiver verifications for receiver id %s", receiverId) + } + + return &receiverVerifications[0], nil +} + // Insert inserts a new receiver verification func (m ReceiverVerificationModel) Insert(ctx context.Context, sqlExec db.SQLExecuter, verificationInsert ReceiverVerificationInsert) (string, error) { err := verificationInsert.Validate() @@ -106,11 +131,17 @@ func (m ReceiverVerificationModel) Insert(ctx context.Context, sqlExec db.SQLExe INSERT INTO receiver_verifications ( receiver_id, verification_field, - hashed_value - ) VALUES ($1, $2, $3) + hashed_value%s + ) VALUES ($1, $2, $3%s) ` - _, err = sqlExec.ExecContext(ctx, query, verificationInsert.ReceiverID, verificationInsert.VerificationField, hashedValue) + if verificationInsert.UpdatedAt != nil { + query = fmt.Sprintf(query, ",\nupdated_at\n", ", $4") + _, err = sqlExec.ExecContext(ctx, query, verificationInsert.ReceiverID, verificationInsert.VerificationField, hashedValue, *verificationInsert.UpdatedAt) + } else { + query = fmt.Sprintf(query, "", "") + _, err = sqlExec.ExecContext(ctx, query, verificationInsert.ReceiverID, verificationInsert.VerificationField, hashedValue) + } if err != nil { return "", fmt.Errorf("error inserting receiver verification: %w", err) diff --git a/internal/data/receiver_verification_test.go b/internal/data/receiver_verification_test.go index 9e1238c51..016d1e323 100644 --- a/internal/data/receiver_verification_test.go +++ b/internal/data/receiver_verification_test.go @@ -2,6 +2,7 @@ package data import ( "context" + "fmt" "testing" "time" @@ -125,6 +126,59 @@ func Test_ReceiverVerificationModel_GetAllByReceiverId(t *testing.T) { }) } +func Test_ReceiverVerificationModel_GetReceiverVerificationByReceiverId(t *testing.T) { + dbt := dbtest.Open(t) + defer dbt.Close() + + dbConnectionPool, err := db.OpenDBConnectionPool(dbt.DSN) + require.NoError(t, err) + defer dbConnectionPool.Close() + + ctx := context.Background() + + receiver := CreateReceiverFixture(t, ctx, dbConnectionPool, &Receiver{}) + + t.Run("returns error when the receiver has no verifications registered", func(t *testing.T) { + receiverVerificationModel := ReceiverVerificationModel{} + _, err := receiverVerificationModel.GetLatestByReceiverId(ctx, dbConnectionPool, receiver.ID) + require.Error(t, err, fmt.Errorf("cannot query any receiver verifications for receiver id %s", receiver.ID)) + }) + + t.Run("returns the latest receiver verification for a list of receiver verifications", func(t *testing.T) { + earlierTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ + ReceiverID: receiver.ID, + VerificationField: VerificationFieldDateOfBirth, + VerificationValue: "1990-01-01", + UpdatedAt: &earlierTime, + }) + CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ + ReceiverID: receiver.ID, + VerificationField: VerificationFieldPin, + VerificationValue: "1234", + UpdatedAt: &earlierTime, + }) + verification3 := CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ + ReceiverID: receiver.ID, + VerificationField: VerificationFieldNationalID, + VerificationValue: "5678", + }) + + receiverVerificationModel := ReceiverVerificationModel{} + actualVerification, err := receiverVerificationModel.GetLatestByReceiverId(ctx, dbConnectionPool, receiver.ID) + require.NoError(t, err) + + assert.Equal(t, + ReceiverVerification{ + ReceiverID: receiver.ID, + VerificationField: VerificationFieldNationalID, + HashedValue: verification3.HashedValue, + CreatedAt: verification3.CreatedAt, + UpdatedAt: verification3.UpdatedAt, + }, *actualVerification) + }) +} + func Test_ReceiverVerificationModel_Insert(t *testing.T) { dbt := dbtest.Open(t) defer dbt.Close() diff --git a/internal/htmltemplate/tmpl/receiver_register.tmpl b/internal/htmltemplate/tmpl/receiver_register.tmpl index d74b02781..a44c04a31 100644 --- a/internal/htmltemplate/tmpl/receiver_register.tmpl +++ b/internal/htmltemplate/tmpl/receiver_register.tmpl @@ -119,14 +119,39 @@ />
- + {{ if .VerificationField eq "DATE_OF_BIRTH" }} + + + {{ else if .VerificationField eq "PIN" }} + + + {{ else if .VerificationField eq "NATIONAL_ID" }} + + + {{ end }} +
diff --git a/internal/serve/httphandler/receiver_registration.go b/internal/serve/httphandler/receiver_registration.go index bffb11593..89390d9db 100644 --- a/internal/serve/httphandler/receiver_registration.go +++ b/internal/serve/httphandler/receiver_registration.go @@ -8,21 +8,25 @@ import ( "github.com/stellar/go/support/log" "github.com/stellar/stellar-disbursement-platform-backend/internal/anchorplatform" "github.com/stellar/stellar-disbursement-platform-backend/internal/data" + "github.com/stellar/stellar-disbursement-platform-backend/internal/db" htmlTpl "github.com/stellar/stellar-disbursement-platform-backend/internal/htmltemplate" "github.com/stellar/stellar-disbursement-platform-backend/internal/serve/httperror" ) type ReceiverRegistrationHandler struct { - ReceiverWalletModel *data.ReceiverWalletModel - ReCAPTCHASiteKey string + ReceiverWalletModel *data.ReceiverWalletModel + ReceiverVerificationModel *data.ReceiverVerificationModel + DBConnectionPool db.DBConnectionPool + ReCAPTCHASiteKey string } type ReceiverRegistrationData struct { - StellarAccount string - JWTToken string - Title string - Message string - ReCAPTCHASiteKey string + StellarAccount string + JWTToken string + Title string + Message string + ReCAPTCHASiteKey string + VerificationField data.VerificationField } // ServeHTTP will serve the SEP-24 deposit page needed to register users. @@ -70,6 +74,14 @@ func (h ReceiverRegistrationHandler) ServeHTTP(w http.ResponseWriter, r *http.Re htmlTemplateName = "receiver_registered_successfully.tmpl" tmplData.Title = "Registration Complete 🎉" tmplData.Message = "Your Stellar wallet has been registered successfully!" + } else { + latestReceiverVerification, err := h.ReceiverVerificationModel.GetLatestByReceiverId(ctx, h.DBConnectionPool, rw.Receiver.ID) + if err != nil { + httperror.InternalError(ctx, "Cannot find receiver verifications for receiver wallet", err, nil).Render(w) + return + } + + tmplData.VerificationField = latestReceiverVerification.VerificationField } registerPage, err := htmlTpl.ExecuteHTMLTemplate(htmlTemplateName, tmplData) From e76d74ccd8166aba879948c8dc06e19d864349e2 Mon Sep 17 00:00:00 2001 From: Erica Date: Mon, 11 Dec 2023 15:08:46 -0800 Subject: [PATCH 2/9] wip --- internal/data/models.go | 2 +- internal/data/receiver_verification.go | 21 ++++++++------ .../htmltemplate/tmpl/receiver_register.tmpl | 28 +++++++------------ .../httphandler/receiver_registration.go | 9 +++--- .../publicfiles/js/receiver_registration.js | 4 ++- internal/serve/serve.go | 7 ++++- 6 files changed, 37 insertions(+), 34 deletions(-) diff --git a/internal/data/models.go b/internal/data/models.go index e8946829c..83d83be11 100644 --- a/internal/data/models.go +++ b/internal/data/models.go @@ -42,7 +42,7 @@ func NewModels(dbConnectionPool db.DBConnectionPool) (*Models, error) { Payment: &PaymentModel{dbConnectionPool: dbConnectionPool}, Receiver: &ReceiverModel{}, DisbursementInstructions: NewDisbursementInstructionModel(dbConnectionPool), - ReceiverVerification: &ReceiverVerificationModel{}, + ReceiverVerification: &ReceiverVerificationModel{dbConnectionPool: dbConnectionPool}, ReceiverWallet: &ReceiverWalletModel{dbConnectionPool: dbConnectionPool}, DisbursementReceivers: &DisbursementReceiverModel{dbConnectionPool: dbConnectionPool}, Message: &MessageModel{dbConnectionPool: dbConnectionPool}, diff --git a/internal/data/receiver_verification.go b/internal/data/receiver_verification.go index af87670cf..0cdbe2cab 100644 --- a/internal/data/receiver_verification.go +++ b/internal/data/receiver_verification.go @@ -24,7 +24,9 @@ type ReceiverVerification struct { FailedAt *time.Time `db:"failed_at"` } -type ReceiverVerificationModel struct{} +type ReceiverVerificationModel struct{ + dbConnectionPool db.DBConnectionPool +} type ReceiverVerificationInsert struct { ReceiverID string `db:"receiver_id"` @@ -49,7 +51,7 @@ func (rvi *ReceiverVerificationInsert) Validate() error { } // GetByReceiverIDsAndVerificationField returns receiver verifications by receiver IDs and verification type. -func (m ReceiverVerificationModel) GetByReceiverIDsAndVerificationField(ctx context.Context, sqlExec db.SQLExecuter, receiverIds []string, verificationField VerificationField) ([]*ReceiverVerification, error) { +func (m *ReceiverVerificationModel) GetByReceiverIDsAndVerificationField(ctx context.Context, sqlExec db.SQLExecuter, receiverIds []string, verificationField VerificationField) ([]*ReceiverVerification, error) { receiverVerifications := []*ReceiverVerification{} query := ` SELECT @@ -75,7 +77,7 @@ func (m ReceiverVerificationModel) GetByReceiverIDsAndVerificationField(ctx cont } // GetAllByReceiverId returns all receiver verifications by receiver id. -func (m ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlExec db.SQLExecuter, receiverId string) ([]ReceiverVerification, error) { +func (m *ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlExec db.SQLExecuter, receiverId string) ([]ReceiverVerification, error) { receiverVerifications := []ReceiverVerification{} query := ` SELECT @@ -92,7 +94,7 @@ func (m ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlEx return receiverVerifications, nil } -func (m *ReceiverVerificationModel) GetLatestByReceiverId(ctx context.Context, sqlExec db.SQLExecuter, receiverId string) (*ReceiverVerification, error) { +func (m *ReceiverVerificationModel) GetLatestByReceiverId(ctx context.Context, receiverId string) (*ReceiverVerification, error) { receiverVerifications := []ReceiverVerification{} query := ` SELECT @@ -104,11 +106,14 @@ func (m *ReceiverVerificationModel) GetLatestByReceiverId(ctx context.Context, s ORDER BY updated_at DESC ` + log.Ctx(ctx).Info("reached here 2") - err := sqlExec.SelectContext(ctx, &receiverVerifications, query, receiverId) + err := m.dbConnectionPool.SelectContext(ctx, &receiverVerifications, query, receiverId) if err != nil { return nil, fmt.Errorf("error querying receiver verifications: %w", err) } + log.Ctx(ctx).Info("reached here 3") + if len(receiverVerifications) == 0 { return nil, fmt.Errorf("cannot query any receiver verifications for receiver id %s", receiverId) } @@ -117,7 +122,7 @@ func (m *ReceiverVerificationModel) GetLatestByReceiverId(ctx context.Context, s } // Insert inserts a new receiver verification -func (m ReceiverVerificationModel) Insert(ctx context.Context, sqlExec db.SQLExecuter, verificationInsert ReceiverVerificationInsert) (string, error) { +func (m *ReceiverVerificationModel) Insert(ctx context.Context, sqlExec db.SQLExecuter, verificationInsert ReceiverVerificationInsert) (string, error) { err := verificationInsert.Validate() if err != nil { return "", fmt.Errorf("error validating receiver verification insert: %w", err) @@ -151,7 +156,7 @@ func (m ReceiverVerificationModel) Insert(ctx context.Context, sqlExec db.SQLExe } // UpdateVerificationValue updates the hashed value of a receiver verification. -func (m ReceiverVerificationModel) UpdateVerificationValue(ctx context.Context, +func (m *ReceiverVerificationModel) UpdateVerificationValue(ctx context.Context, sqlExec db.SQLExecuter, receiverID string, verificationField VerificationField, @@ -179,7 +184,7 @@ func (m ReceiverVerificationModel) UpdateVerificationValue(ctx context.Context, } // UpdateVerificationValue updates the hashed value of a receiver verification. -func (m ReceiverVerificationModel) UpdateReceiverVerification(ctx context.Context, receiverVerification ReceiverVerification, sqlExec db.SQLExecuter) error { +func (m *ReceiverVerificationModel) UpdateReceiverVerification(ctx context.Context, receiverVerification ReceiverVerification, sqlExec db.SQLExecuter) error { query := ` UPDATE receiver_verifications diff --git a/internal/htmltemplate/tmpl/receiver_register.tmpl b/internal/htmltemplate/tmpl/receiver_register.tmpl index a44c04a31..b081f55e2 100644 --- a/internal/htmltemplate/tmpl/receiver_register.tmpl +++ b/internal/htmltemplate/tmpl/receiver_register.tmpl @@ -119,39 +119,31 @@ />
- {{ if .VerificationField eq "DATE_OF_BIRTH" }} + {{ if eq .VerificationField "DATE_OF_BIRTH" }} - {{ else if .VerificationField eq "PIN" }} + {{ else if eq .VerificationField "PIN" }} - {{ else if .VerificationField eq "NATIONAL_ID" }} + {{ else if eq .VerificationField "NATIONAL_ID" }} {{ end }} -
diff --git a/internal/serve/httphandler/receiver_registration.go b/internal/serve/httphandler/receiver_registration.go index 89390d9db..1d2c7d676 100644 --- a/internal/serve/httphandler/receiver_registration.go +++ b/internal/serve/httphandler/receiver_registration.go @@ -8,7 +8,6 @@ import ( "github.com/stellar/go/support/log" "github.com/stellar/stellar-disbursement-platform-backend/internal/anchorplatform" "github.com/stellar/stellar-disbursement-platform-backend/internal/data" - "github.com/stellar/stellar-disbursement-platform-backend/internal/db" htmlTpl "github.com/stellar/stellar-disbursement-platform-backend/internal/htmltemplate" "github.com/stellar/stellar-disbursement-platform-backend/internal/serve/httperror" ) @@ -16,7 +15,7 @@ import ( type ReceiverRegistrationHandler struct { ReceiverWalletModel *data.ReceiverWalletModel ReceiverVerificationModel *data.ReceiverVerificationModel - DBConnectionPool db.DBConnectionPool + Models *data.Models ReCAPTCHASiteKey string } @@ -75,13 +74,13 @@ func (h ReceiverRegistrationHandler) ServeHTTP(w http.ResponseWriter, r *http.Re tmplData.Title = "Registration Complete 🎉" tmplData.Message = "Your Stellar wallet has been registered successfully!" } else { - latestReceiverVerification, err := h.ReceiverVerificationModel.GetLatestByReceiverId(ctx, h.DBConnectionPool, rw.Receiver.ID) + /*latestReceiverVerification, err := h.Models.ReceiverVerification.GetLatestByReceiverId(ctx, "abc") if err != nil { httperror.InternalError(ctx, "Cannot find receiver verifications for receiver wallet", err, nil).Render(w) return - } + }*/ - tmplData.VerificationField = latestReceiverVerification.VerificationField + tmplData.VerificationField = data.VerificationFieldPin } registerPage, err := htmlTpl.ExecuteHTMLTemplate(htmlTemplateName, tmplData) diff --git a/internal/serve/publicfiles/js/receiver_registration.js b/internal/serve/publicfiles/js/receiver_registration.js index 2065d3572..37fd149ff 100644 --- a/internal/serve/publicfiles/js/receiver_registration.js +++ b/internal/serve/publicfiles/js/receiver_registration.js @@ -169,6 +169,8 @@ async function submitOtp(event) { ); const otpEl = document.getElementById("otp"); const verificationEl = document.getElementById("verification"); + const verificationType = verificationEl.className; + console.log(verificationType); const buttonEls = passcodeSectionEl.querySelectorAll("[data-button]"); @@ -213,7 +215,7 @@ async function submitOtp(event) { phone_number: phoneNumber, otp: otp, verification: verification, - verification_type: "date_of_birth", + verification_type: verificationType, recaptcha_token: reCAPTCHATokenEl.value, }), }); diff --git a/internal/serve/serve.go b/internal/serve/serve.go index e97c1384f..73892417b 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -380,7 +380,12 @@ func handleHTTP(o ServeOptions) *chi.Mux { mux.Route("/wallet-registration", func(r chi.Router) { sep24QueryTokenAuthenticationMiddleware := anchorplatform.SEP24QueryTokenAuthenticateMiddleware(o.sep24JWTManager, o.NetworkPassphrase) - r.With(sep24QueryTokenAuthenticationMiddleware).Get("/start", httphandler.ReceiverRegistrationHandler{ReceiverWalletModel: o.Models.ReceiverWallet, ReCAPTCHASiteKey: o.ReCAPTCHASiteKey}.ServeHTTP) // This loads the SEP-24 PII registration webpage. + r.With(sep24QueryTokenAuthenticationMiddleware).Get("/start", httphandler.ReceiverRegistrationHandler{ + ReceiverWalletModel: o.Models.ReceiverWallet, + ReceiverVerificationModel: o.Models.ReceiverVerification, + Models: o.Models, + ReCAPTCHASiteKey: o.ReCAPTCHASiteKey, + }.ServeHTTP) // This loads the SEP-24 PII registration webpage. sep24HeaderTokenAuthenticationMiddleware := anchorplatform.SEP24HeaderTokenAuthenticateMiddleware(o.sep24JWTManager, o.NetworkPassphrase) r.With(sep24HeaderTokenAuthenticationMiddleware).Post("/otp", httphandler.ReceiverSendOTPHandler{Models: o.Models, SMSMessengerClient: o.SMSMessengerClient, ReCAPTCHAValidator: reCAPTCHAValidator}.ServeHTTP) From cfb8bb4a214af6b550590db1f1f6414de2bc4fea Mon Sep 17 00:00:00 2001 From: Erica Date: Tue, 12 Dec 2023 14:36:43 -0800 Subject: [PATCH 3/9] verification works e2e --- internal/data/receiver_verification.go | 21 +++++++-------- internal/data/receiver_verification_test.go | 2 +- .../htmltemplate/tmpl/receiver_register.tmpl | 12 ++++++--- .../httphandler/receiver_registration.go | 4 +-- .../httphandler/receiver_send_otp_handler.go | 18 +++++++++++-- .../publicfiles/js/receiver_registration.js | 27 +++++++++++++++---- internal/serve/serve.go | 6 ++--- 7 files changed, 63 insertions(+), 27 deletions(-) diff --git a/internal/data/receiver_verification.go b/internal/data/receiver_verification.go index 0cdbe2cab..003b1c11d 100644 --- a/internal/data/receiver_verification.go +++ b/internal/data/receiver_verification.go @@ -24,7 +24,7 @@ type ReceiverVerification struct { FailedAt *time.Time `db:"failed_at"` } -type ReceiverVerificationModel struct{ +type ReceiverVerificationModel struct { dbConnectionPool db.DBConnectionPool } @@ -94,28 +94,27 @@ func (m *ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlE return receiverVerifications, nil } -func (m *ReceiverVerificationModel) GetLatestByReceiverId(ctx context.Context, receiverId string) (*ReceiverVerification, error) { +func (m *ReceiverVerificationModel) GetLatestByPhoneNumber(ctx context.Context, phoneNumber string) (*ReceiverVerification, error) { receiverVerifications := []ReceiverVerification{} query := ` SELECT - * + rv.* FROM - receiver_verifications - WHERE - receiver_id = $1 + receiver_verifications rv + JOIN receivers r + ON rv.receiver_id = r.id + WHERE r.phone_number = $1 ORDER BY - updated_at DESC + rv.updated_at DESC ` - log.Ctx(ctx).Info("reached here 2") - err := m.dbConnectionPool.SelectContext(ctx, &receiverVerifications, query, receiverId) + err := m.dbConnectionPool.SelectContext(ctx, &receiverVerifications, query, phoneNumber) if err != nil { return nil, fmt.Errorf("error querying receiver verifications: %w", err) } - log.Ctx(ctx).Info("reached here 3") if len(receiverVerifications) == 0 { - return nil, fmt.Errorf("cannot query any receiver verifications for receiver id %s", receiverId) + return nil, fmt.Errorf("cannot query any receiver verifications for phone number %s: %w", phoneNumber, err) } return &receiverVerifications[0], nil diff --git a/internal/data/receiver_verification_test.go b/internal/data/receiver_verification_test.go index 016d1e323..fa0ff35e3 100644 --- a/internal/data/receiver_verification_test.go +++ b/internal/data/receiver_verification_test.go @@ -140,7 +140,7 @@ func Test_ReceiverVerificationModel_GetReceiverVerificationByReceiverId(t *testi t.Run("returns error when the receiver has no verifications registered", func(t *testing.T) { receiverVerificationModel := ReceiverVerificationModel{} - _, err := receiverVerificationModel.GetLatestByReceiverId(ctx, dbConnectionPool, receiver.ID) + _, err := receiverVerificationModel.GetLatestByPhoneNumber(ctx, dbConnectionPool, "+13334445555") require.Error(t, err, fmt.Errorf("cannot query any receiver verifications for receiver id %s", receiver.ID)) }) diff --git a/internal/htmltemplate/tmpl/receiver_register.tmpl b/internal/htmltemplate/tmpl/receiver_register.tmpl index b081f55e2..a034b0e97 100644 --- a/internal/htmltemplate/tmpl/receiver_register.tmpl +++ b/internal/htmltemplate/tmpl/receiver_register.tmpl @@ -87,7 +87,7 @@
- +

Enter passcode

@@ -119,7 +119,7 @@ />
- {{ if eq .VerificationField "DATE_OF_BIRTH" }} + + +
diff --git a/internal/serve/httphandler/receiver_registration.go b/internal/serve/httphandler/receiver_registration.go index 1d2c7d676..706d1fe91 100644 --- a/internal/serve/httphandler/receiver_registration.go +++ b/internal/serve/httphandler/receiver_registration.go @@ -15,7 +15,7 @@ import ( type ReceiverRegistrationHandler struct { ReceiverWalletModel *data.ReceiverWalletModel ReceiverVerificationModel *data.ReceiverVerificationModel - Models *data.Models + Models *data.Models ReCAPTCHASiteKey string } @@ -80,7 +80,7 @@ func (h ReceiverRegistrationHandler) ServeHTTP(w http.ResponseWriter, r *http.Re return }*/ - tmplData.VerificationField = data.VerificationFieldPin + //tmplData.VerificationField = data.VerificationFieldPin } registerPage, err := htmlTpl.ExecuteHTMLTemplate(htmlTemplateName, tmplData) diff --git a/internal/serve/httphandler/receiver_send_otp_handler.go b/internal/serve/httphandler/receiver_send_otp_handler.go index cc76b74e7..087785f5c 100644 --- a/internal/serve/httphandler/receiver_send_otp_handler.go +++ b/internal/serve/httphandler/receiver_send_otp_handler.go @@ -35,7 +35,8 @@ type ReceiverSendOTPRequest struct { } type ReceiverSendOTPResponseBody struct { - Message string `json:"message"` + Message string `json:"message"` + VerificationType data.VerificationField `json:"verification_type"` } func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -109,6 +110,13 @@ func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request return } + log.Infof("phone number is %s", receiverSendOTPRequest.PhoneNumber) + receiverVerification, err := h.Models.ReceiverVerification.GetLatestByPhoneNumber(ctx, receiverSendOTPRequest.PhoneNumber) + if err != nil { + httperror.InternalError(ctx, "Cannot find receiver verification associated with phone number", err, nil).Render(w) + return + } + if numberOfUpdatedRows < 1 { log.Ctx(ctx).Warnf("updated no rows in receiver send OTP handler for phone number: %s", utils.TruncateString(receiverSendOTPRequest.PhoneNumber, len(receiverSendOTPRequest.PhoneNumber)/4)) } else { @@ -148,8 +156,14 @@ func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request } } + fmt.Println("=======") + fmt.Println(receiverVerification.VerificationField) + + fmt.Println("======") + response := ReceiverSendOTPResponseBody{ - Message: "if your phone number is registered, you'll receive an OTP", + Message: "if your phone number is registered, you'll receive an OTP", + VerificationType: receiverVerification.VerificationField, } httpjson.RenderStatus(w, http.StatusOK, response, httpjson.JSON) } diff --git a/internal/serve/publicfiles/js/receiver_registration.js b/internal/serve/publicfiles/js/receiver_registration.js index 37fd149ff..17c99c09d 100644 --- a/internal/serve/publicfiles/js/receiver_registration.js +++ b/internal/serve/publicfiles/js/receiver_registration.js @@ -51,9 +51,9 @@ async function sendSms(phoneNumber, reCAPTCHAToken, onSuccess, onError) { recaptcha_token: reCAPTCHAToken, }), }); - await request.json(); + const resp = await request.json(); - onSuccess(); + onSuccess(resp.verification_type); } catch (error) { onError(error); } @@ -96,6 +96,8 @@ async function submitPhoneNumber(event) { "#g-recaptcha-response" ); const buttonEls = phoneNumberSectionEl.querySelectorAll("[data-button]"); + const verificationTypeTitle = document.querySelector("label[for='verification']"); + const verificationTypeInput = document.querySelector("#verification"); // id verification if (!reCAPTCHATokenEl || !reCAPTCHATokenEl.value) { toggleErrorNotification( @@ -133,7 +135,23 @@ async function submitPhoneNumber(event) { return; } - function showNextPage() { + function showNextPage(verificationType) { + if(verificationType === "DATE_OF_BIRTH") { + verificationTypeTitle.textContent = "Date_of_birth" + verificationTypeInput.name = "date_of_birth" + verificationTypeInput.type = "date" + } + else if(verificationType === "NATIONAL_ID") { + verificationTypeTitle.textContent = "National_ID" + verificationTypeInput.name = "national_id" + verificationTypeInput.type = "text" + } + else if(verificationType === "PIN") { + verificationTypeTitle.textContent = "Pin" + verificationTypeInput.name = "pin" + verificationTypeInput.type = "text" + } + phoneNumberSectionEl.style.display = "none"; reCAPTCHATokenEl.style.display = "none"; passcodeSectionEl.style.display = "flex"; @@ -169,8 +187,7 @@ async function submitOtp(event) { ); const otpEl = document.getElementById("otp"); const verificationEl = document.getElementById("verification"); - const verificationType = verificationEl.className; - console.log(verificationType); + const verificationType = verificationEl.getAttribute("name"); const buttonEls = passcodeSectionEl.querySelectorAll("[data-button]"); diff --git a/internal/serve/serve.go b/internal/serve/serve.go index 73892417b..b24905af2 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -381,10 +381,10 @@ func handleHTTP(o ServeOptions) *chi.Mux { mux.Route("/wallet-registration", func(r chi.Router) { sep24QueryTokenAuthenticationMiddleware := anchorplatform.SEP24QueryTokenAuthenticateMiddleware(o.sep24JWTManager, o.NetworkPassphrase) r.With(sep24QueryTokenAuthenticationMiddleware).Get("/start", httphandler.ReceiverRegistrationHandler{ - ReceiverWalletModel: o.Models.ReceiverWallet, + ReceiverWalletModel: o.Models.ReceiverWallet, ReceiverVerificationModel: o.Models.ReceiverVerification, - Models: o.Models, - ReCAPTCHASiteKey: o.ReCAPTCHASiteKey, + Models: o.Models, + ReCAPTCHASiteKey: o.ReCAPTCHASiteKey, }.ServeHTTP) // This loads the SEP-24 PII registration webpage. sep24HeaderTokenAuthenticationMiddleware := anchorplatform.SEP24HeaderTokenAuthenticateMiddleware(o.sep24JWTManager, o.NetworkPassphrase) From 03554dd7b5c058b8f6ad12d722b81e509cf013f1 Mon Sep 17 00:00:00 2001 From: Erica Date: Thu, 14 Dec 2023 01:16:13 -0800 Subject: [PATCH 4/9] tests --- internal/data/receiver_verification.go | 2 +- internal/data/receiver_verification_test.go | 14 ++--- .../htmltemplate/tmpl/receiver_register.tmpl | 37 +++---------- .../httphandler/receiver_registration.go | 8 --- .../httphandler/receiver_send_otp_handler.go | 18 +++---- .../receiver_send_otp_handler_test.go | 54 +++++++++++++++++-- 6 files changed, 71 insertions(+), 62 deletions(-) diff --git a/internal/data/receiver_verification.go b/internal/data/receiver_verification.go index 003b1c11d..7079cfc42 100644 --- a/internal/data/receiver_verification.go +++ b/internal/data/receiver_verification.go @@ -110,7 +110,7 @@ func (m *ReceiverVerificationModel) GetLatestByPhoneNumber(ctx context.Context, err := m.dbConnectionPool.SelectContext(ctx, &receiverVerifications, query, phoneNumber) if err != nil { - return nil, fmt.Errorf("error querying receiver verifications: %w", err) + return nil, fmt.Errorf("error querying receiver verifications for phone number %s: %w", phoneNumber, err) } if len(receiverVerifications) == 0 { diff --git a/internal/data/receiver_verification_test.go b/internal/data/receiver_verification_test.go index fa0ff35e3..9f955d3c3 100644 --- a/internal/data/receiver_verification_test.go +++ b/internal/data/receiver_verification_test.go @@ -136,12 +136,14 @@ func Test_ReceiverVerificationModel_GetReceiverVerificationByReceiverId(t *testi ctx := context.Background() - receiver := CreateReceiverFixture(t, ctx, dbConnectionPool, &Receiver{}) + receiver := CreateReceiverFixture(t, ctx, dbConnectionPool, &Receiver{ + PhoneNumber: "+13334445555", + }) t.Run("returns error when the receiver has no verifications registered", func(t *testing.T) { - receiverVerificationModel := ReceiverVerificationModel{} - _, err := receiverVerificationModel.GetLatestByPhoneNumber(ctx, dbConnectionPool, "+13334445555") - require.Error(t, err, fmt.Errorf("cannot query any receiver verifications for receiver id %s", receiver.ID)) + receiverVerificationModel := ReceiverVerificationModel{dbConnectionPool: dbConnectionPool} + _, err := receiverVerificationModel.GetLatestByPhoneNumber(ctx, receiver.PhoneNumber) + require.Error(t, err, fmt.Errorf("cannot query any receiver verifications for phone number %s", receiver.PhoneNumber)) }) t.Run("returns the latest receiver verification for a list of receiver verifications", func(t *testing.T) { @@ -164,8 +166,8 @@ func Test_ReceiverVerificationModel_GetReceiverVerificationByReceiverId(t *testi VerificationValue: "5678", }) - receiverVerificationModel := ReceiverVerificationModel{} - actualVerification, err := receiverVerificationModel.GetLatestByReceiverId(ctx, dbConnectionPool, receiver.ID) + receiverVerificationModel := ReceiverVerificationModel{dbConnectionPool: dbConnectionPool} + actualVerification, err := receiverVerificationModel.GetLatestByPhoneNumber(ctx, receiver.PhoneNumber) require.NoError(t, err) assert.Equal(t, diff --git a/internal/htmltemplate/tmpl/receiver_register.tmpl b/internal/htmltemplate/tmpl/receiver_register.tmpl index a034b0e97..aad4c92b8 100644 --- a/internal/htmltemplate/tmpl/receiver_register.tmpl +++ b/internal/htmltemplate/tmpl/receiver_register.tmpl @@ -119,37 +119,12 @@ />
- - - + +
diff --git a/internal/serve/httphandler/receiver_registration.go b/internal/serve/httphandler/receiver_registration.go index 706d1fe91..0cc5b96db 100644 --- a/internal/serve/httphandler/receiver_registration.go +++ b/internal/serve/httphandler/receiver_registration.go @@ -73,14 +73,6 @@ func (h ReceiverRegistrationHandler) ServeHTTP(w http.ResponseWriter, r *http.Re htmlTemplateName = "receiver_registered_successfully.tmpl" tmplData.Title = "Registration Complete 🎉" tmplData.Message = "Your Stellar wallet has been registered successfully!" - } else { - /*latestReceiverVerification, err := h.Models.ReceiverVerification.GetLatestByReceiverId(ctx, "abc") - if err != nil { - httperror.InternalError(ctx, "Cannot find receiver verifications for receiver wallet", err, nil).Render(w) - return - }*/ - - //tmplData.VerificationField = data.VerificationFieldPin } registerPage, err := htmlTpl.ExecuteHTMLTemplate(htmlTemplateName, tmplData) diff --git a/internal/serve/httphandler/receiver_send_otp_handler.go b/internal/serve/httphandler/receiver_send_otp_handler.go index 087785f5c..97089034c 100644 --- a/internal/serve/httphandler/receiver_send_otp_handler.go +++ b/internal/serve/httphandler/receiver_send_otp_handler.go @@ -91,6 +91,12 @@ func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request return } + receiverVerification, err := h.Models.ReceiverVerification.GetLatestByPhoneNumber(ctx, receiverSendOTPRequest.PhoneNumber) + if err != nil { + httperror.InternalError(ctx, "Cannot find latest receiver verification for receiver", err, nil).Render(w) + return + } + // Generate a new 6 digits OTP newOTP, err := utils.RandomString(6, utils.NumberBytes) if err != nil { @@ -110,13 +116,6 @@ func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request return } - log.Infof("phone number is %s", receiverSendOTPRequest.PhoneNumber) - receiverVerification, err := h.Models.ReceiverVerification.GetLatestByPhoneNumber(ctx, receiverSendOTPRequest.PhoneNumber) - if err != nil { - httperror.InternalError(ctx, "Cannot find receiver verification associated with phone number", err, nil).Render(w) - return - } - if numberOfUpdatedRows < 1 { log.Ctx(ctx).Warnf("updated no rows in receiver send OTP handler for phone number: %s", utils.TruncateString(receiverSendOTPRequest.PhoneNumber, len(receiverSendOTPRequest.PhoneNumber)/4)) } else { @@ -156,11 +155,6 @@ func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request } } - fmt.Println("=======") - fmt.Println(receiverVerification.VerificationField) - - fmt.Println("======") - response := ReceiverSendOTPResponseBody{ Message: "if your phone number is registered, you'll receive an OTP", VerificationType: receiverVerification.VerificationField, diff --git a/internal/serve/httphandler/receiver_send_otp_handler_test.go b/internal/serve/httphandler/receiver_send_otp_handler_test.go index 6be199eee..4c7f8193a 100644 --- a/internal/serve/httphandler/receiver_send_otp_handler_test.go +++ b/internal/serve/httphandler/receiver_send_otp_handler_test.go @@ -53,9 +53,14 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { ctx := context.Background() - receiver1 := data.CreateReceiverFixture(t, ctx, dbConnectionPool, &data.Receiver{PhoneNumber: "+380443973607"}) + phoneNumber := "+380443973607" + receiver1 := data.CreateReceiverFixture(t, ctx, dbConnectionPool, &data.Receiver{PhoneNumber: phoneNumber}) receiver2 := data.CreateReceiverFixture(t, ctx, dbConnectionPool, &data.Receiver{}) wallet1 := data.CreateWalletFixture(t, ctx, dbConnectionPool, "testWallet", "https://home.page", "home.page", "wallet123://") + data.CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, data.ReceiverVerificationInsert{ + ReceiverID: receiver1.ID, + VerificationField: data.VerificationFieldDateOfBirth, + }) _ = data.CreateReceiverWalletFixture(t, ctx, dbConnectionPool, receiver1.ID, wallet1.ID, data.RegisteredReceiversWalletStatus) _ = data.CreateReceiverWalletFixture(t, ctx, dbConnectionPool, receiver2.ID, wallet1.ID, data.RegisteredReceiversWalletStatus) @@ -157,7 +162,7 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { assert.JSONEq(t, `{"error": "request invalid", "extras": {"phone_number": "invalid phone number provided"}}`, string(respBody)) }) - t.Run("returns 200 - Ok if the token is in the request context and body it's valid", func(t *testing.T) { + t.Run("returns 200 - Ok if the token is in the request context and body is valid", func(t *testing.T) { reCAPTCHAValidator. On("IsTokenValid", mock.Anything, "XyZ"). Return(true, nil). @@ -193,7 +198,7 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Contains(t, resp.Header.Get("Content-Type"), "/json; charset=utf-8") - assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP"}`) + assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP", "verification_type":"DATE_OF_BIRTH"}`) }) t.Run("returns 200 - parses a custom OTP message template successfully", func(t *testing.T) { @@ -237,7 +242,7 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Contains(t, resp.Header.Get("Content-Type"), "/json; charset=utf-8") - assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP"}`) + assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP", "verification_type":"DATE_OF_BIRTH"}`) }) t.Run("returns 500 - InternalServerError when something goes wrong when sending the SMS", func(t *testing.T) { @@ -309,6 +314,47 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { assert.JSONEq(t, wantsBody, string(respBody)) }) + t.Run("returns 500 - InternalServerError if phone number is not associated with receiver verification", func(t *testing.T) { + requestSendOTP := ReceiverSendOTPRequest{ + PhoneNumber: "+14152223333", + ReCAPTCHAToken: "XyZ", + } + reqBody, err := json.Marshal(requestSendOTP) + require.NoError(t, err) + + reCAPTCHAValidator. + On("IsTokenValid", mock.Anything, "XyZ"). + Return(true, nil). + Once() + req, err := http.NewRequest(http.MethodPost, "/wallet-registration/otp", strings.NewReader(string(reqBody))) + require.NoError(t, err) + + validClaims := &anchorplatform.SEP24JWTClaims{ + ClientDomainClaim: wallet1.SEP10ClientDomain, + RegisteredClaims: jwt.RegisteredClaims{ + ID: "test-transaction-id", + Subject: "GBLTXF46JTCGMWFJASQLVXMMA36IPYTDCN4EN73HRXCGDCGYBZM3A444", + ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute)), + }, + } + req = req.WithContext(context.WithValue(req.Context(), anchorplatform.SEP24ClaimsContextKey, validClaims)) + + rr := httptest.NewRecorder() + r.ServeHTTP(rr, req) + + resp := rr.Result() + respBody, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + wantsBody := ` + { + "error": "Cannot find latest receiver verification for receiver" + } + ` + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + assert.JSONEq(t, wantsBody, string(respBody)) + }) + t.Run("returns 400 - BadRequest when recaptcha token is invalid", func(t *testing.T) { reCAPTCHAValidator. On("IsTokenValid", mock.Anything, "XyZ"). From 2cfe012616915ea168a7adb19f2c7d329255d7dd Mon Sep 17 00:00:00 2001 From: Erica Date: Thu, 14 Dec 2023 11:07:05 -0800 Subject: [PATCH 5/9] clean up --- internal/data/receiver_verification.go | 4 +-- .../htmltemplate/tmpl/receiver_register.tmpl | 1 - .../httphandler/receiver_registration.go | 17 ++++++------- .../httphandler/receiver_send_otp_handler.go | 8 +++--- .../receiver_send_otp_handler_test.go | 7 +++--- .../publicfiles/js/receiver_registration.js | 25 +++++++++---------- internal/serve/serve.go | 6 ++--- 7 files changed, 30 insertions(+), 38 deletions(-) diff --git a/internal/data/receiver_verification.go b/internal/data/receiver_verification.go index 7079cfc42..454f716d5 100644 --- a/internal/data/receiver_verification.go +++ b/internal/data/receiver_verification.go @@ -94,6 +94,7 @@ func (m *ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlE return receiverVerifications, nil } +// GetLatestByPhoneNumber returns the latest updated receiver verification for some receiver that is associated with a phone number. func (m *ReceiverVerificationModel) GetLatestByPhoneNumber(ctx context.Context, phoneNumber string) (*ReceiverVerification, error) { receiverVerifications := []ReceiverVerification{} query := ` @@ -101,8 +102,7 @@ func (m *ReceiverVerificationModel) GetLatestByPhoneNumber(ctx context.Context, rv.* FROM receiver_verifications rv - JOIN receivers r - ON rv.receiver_id = r.id + JOIN receivers r ON rv.receiver_id = r.id WHERE r.phone_number = $1 ORDER BY rv.updated_at DESC diff --git a/internal/htmltemplate/tmpl/receiver_register.tmpl b/internal/htmltemplate/tmpl/receiver_register.tmpl index aad4c92b8..20b0b5e6c 100644 --- a/internal/htmltemplate/tmpl/receiver_register.tmpl +++ b/internal/htmltemplate/tmpl/receiver_register.tmpl @@ -121,7 +121,6 @@
diff --git a/internal/serve/httphandler/receiver_registration.go b/internal/serve/httphandler/receiver_registration.go index 0cc5b96db..bffb11593 100644 --- a/internal/serve/httphandler/receiver_registration.go +++ b/internal/serve/httphandler/receiver_registration.go @@ -13,19 +13,16 @@ import ( ) type ReceiverRegistrationHandler struct { - ReceiverWalletModel *data.ReceiverWalletModel - ReceiverVerificationModel *data.ReceiverVerificationModel - Models *data.Models - ReCAPTCHASiteKey string + ReceiverWalletModel *data.ReceiverWalletModel + ReCAPTCHASiteKey string } type ReceiverRegistrationData struct { - StellarAccount string - JWTToken string - Title string - Message string - ReCAPTCHASiteKey string - VerificationField data.VerificationField + StellarAccount string + JWTToken string + Title string + Message string + ReCAPTCHASiteKey string } // ServeHTTP will serve the SEP-24 deposit page needed to register users. diff --git a/internal/serve/httphandler/receiver_send_otp_handler.go b/internal/serve/httphandler/receiver_send_otp_handler.go index 97089034c..f4315cb63 100644 --- a/internal/serve/httphandler/receiver_send_otp_handler.go +++ b/internal/serve/httphandler/receiver_send_otp_handler.go @@ -35,8 +35,8 @@ type ReceiverSendOTPRequest struct { } type ReceiverSendOTPResponseBody struct { - Message string `json:"message"` - VerificationType data.VerificationField `json:"verification_type"` + Message string `json:"message"` + VerificationField data.VerificationField `json:"verification_field"` } func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -156,8 +156,8 @@ func (h ReceiverSendOTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request } response := ReceiverSendOTPResponseBody{ - Message: "if your phone number is registered, you'll receive an OTP", - VerificationType: receiverVerification.VerificationField, + Message: "if your phone number is registered, you'll receive an OTP", + VerificationField: receiverVerification.VerificationField, } httpjson.RenderStatus(w, http.StatusOK, response, httpjson.JSON) } diff --git a/internal/serve/httphandler/receiver_send_otp_handler_test.go b/internal/serve/httphandler/receiver_send_otp_handler_test.go index 4c7f8193a..edd44d468 100644 --- a/internal/serve/httphandler/receiver_send_otp_handler_test.go +++ b/internal/serve/httphandler/receiver_send_otp_handler_test.go @@ -198,7 +198,7 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Contains(t, resp.Header.Get("Content-Type"), "/json; charset=utf-8") - assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP", "verification_type":"DATE_OF_BIRTH"}`) + assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP", "verification_field":"DATE_OF_BIRTH"}`) }) t.Run("returns 200 - parses a custom OTP message template successfully", func(t *testing.T) { @@ -242,7 +242,7 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Contains(t, resp.Header.Get("Content-Type"), "/json; charset=utf-8") - assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP", "verification_type":"DATE_OF_BIRTH"}`) + assert.JSONEq(t, string(respBody), `{"message":"if your phone number is registered, you'll receive an OTP", "verification_field":"DATE_OF_BIRTH"}`) }) t.Run("returns 500 - InternalServerError when something goes wrong when sending the SMS", func(t *testing.T) { @@ -319,8 +319,7 @@ func Test_ReceiverSendOTPHandler_ServeHTTP(t *testing.T) { PhoneNumber: "+14152223333", ReCAPTCHAToken: "XyZ", } - reqBody, err := json.Marshal(requestSendOTP) - require.NoError(t, err) + reqBody, _ = json.Marshal(requestSendOTP) reCAPTCHAValidator. On("IsTokenValid", mock.Anything, "XyZ"). diff --git a/internal/serve/publicfiles/js/receiver_registration.js b/internal/serve/publicfiles/js/receiver_registration.js index 17c99c09d..f8d576ace 100644 --- a/internal/serve/publicfiles/js/receiver_registration.js +++ b/internal/serve/publicfiles/js/receiver_registration.js @@ -53,7 +53,7 @@ async function sendSms(phoneNumber, reCAPTCHAToken, onSuccess, onError) { }); const resp = await request.json(); - onSuccess(resp.verification_type); + onSuccess(resp.verification_field); } catch (error) { onError(error); } @@ -97,7 +97,7 @@ async function submitPhoneNumber(event) { ); const buttonEls = phoneNumberSectionEl.querySelectorAll("[data-button]"); const verificationTypeTitle = document.querySelector("label[for='verification']"); - const verificationTypeInput = document.querySelector("#verification"); // id verification + const verificationTypeInput = document.querySelector("#verification"); if (!reCAPTCHATokenEl || !reCAPTCHATokenEl.value) { toggleErrorNotification( @@ -136,20 +136,19 @@ async function submitPhoneNumber(event) { } function showNextPage(verificationType) { + verificationTypeInput.type = "date"; if(verificationType === "DATE_OF_BIRTH") { - verificationTypeTitle.textContent = "Date_of_birth" - verificationTypeInput.name = "date_of_birth" - verificationTypeInput.type = "date" + verificationTypeTitle.textContent = "Date_of_birth"; + verificationTypeInput.name = "date_of_birth"; } else if(verificationType === "NATIONAL_ID") { - verificationTypeTitle.textContent = "National_ID" - verificationTypeInput.name = "national_id" - verificationTypeInput.type = "text" + verificationTypeTitle.textContent = "National_ID"; + verificationTypeInput.name = "national_id"; } else if(verificationType === "PIN") { - verificationTypeTitle.textContent = "Pin" - verificationTypeInput.name = "pin" - verificationTypeInput.type = "text" + verificationTypeTitle.textContent = "Pin"; + verificationTypeInput.name = "pin"; + verificationTypeInput.type = "text"; } phoneNumberSectionEl.style.display = "none"; @@ -187,7 +186,7 @@ async function submitOtp(event) { ); const otpEl = document.getElementById("otp"); const verificationEl = document.getElementById("verification"); - const verificationType = verificationEl.getAttribute("name"); + const verificationField = verificationEl.getAttribute("name"); const buttonEls = passcodeSectionEl.querySelectorAll("[data-button]"); @@ -232,7 +231,7 @@ async function submitOtp(event) { phone_number: phoneNumber, otp: otp, verification: verification, - verification_type: verificationType, + verification_type: verificationField, recaptcha_token: reCAPTCHATokenEl.value, }), }); diff --git a/internal/serve/serve.go b/internal/serve/serve.go index b24905af2..9d58dcd07 100644 --- a/internal/serve/serve.go +++ b/internal/serve/serve.go @@ -381,10 +381,8 @@ func handleHTTP(o ServeOptions) *chi.Mux { mux.Route("/wallet-registration", func(r chi.Router) { sep24QueryTokenAuthenticationMiddleware := anchorplatform.SEP24QueryTokenAuthenticateMiddleware(o.sep24JWTManager, o.NetworkPassphrase) r.With(sep24QueryTokenAuthenticationMiddleware).Get("/start", httphandler.ReceiverRegistrationHandler{ - ReceiverWalletModel: o.Models.ReceiverWallet, - ReceiverVerificationModel: o.Models.ReceiverVerification, - Models: o.Models, - ReCAPTCHASiteKey: o.ReCAPTCHASiteKey, + ReceiverWalletModel: o.Models.ReceiverWallet, + ReCAPTCHASiteKey: o.ReCAPTCHASiteKey, }.ServeHTTP) // This loads the SEP-24 PII registration webpage. sep24HeaderTokenAuthenticationMiddleware := anchorplatform.SEP24HeaderTokenAuthenticateMiddleware(o.sep24JWTManager, o.NetworkPassphrase) From b7dc98ae21fe07fd7a0998475218d248593409e2 Mon Sep 17 00:00:00 2001 From: Erica Date: Thu, 14 Dec 2023 11:15:49 -0800 Subject: [PATCH 6/9] oops --- internal/serve/publicfiles/js/receiver_registration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/serve/publicfiles/js/receiver_registration.js b/internal/serve/publicfiles/js/receiver_registration.js index f8d576ace..eb23a3a56 100644 --- a/internal/serve/publicfiles/js/receiver_registration.js +++ b/internal/serve/publicfiles/js/receiver_registration.js @@ -136,10 +136,11 @@ async function submitPhoneNumber(event) { } function showNextPage(verificationType) { - verificationTypeInput.type = "date"; + verificationTypeInput.type = "text"; if(verificationType === "DATE_OF_BIRTH") { verificationTypeTitle.textContent = "Date_of_birth"; verificationTypeInput.name = "date_of_birth"; + verificationTypeInput.type = "date"; } else if(verificationType === "NATIONAL_ID") { verificationTypeTitle.textContent = "National_ID"; @@ -148,7 +149,6 @@ async function submitPhoneNumber(event) { else if(verificationType === "PIN") { verificationTypeTitle.textContent = "Pin"; verificationTypeInput.name = "pin"; - verificationTypeInput.type = "text"; } phoneNumberSectionEl.style.display = "none"; From 46e280303cefa494d93e8879f9d7bc1da31278fa Mon Sep 17 00:00:00 2001 From: Erica Date: Thu, 14 Dec 2023 15:32:48 -0800 Subject: [PATCH 7/9] naming --- .../htmltemplate/tmpl/receiver_register.tmpl | 2 +- .../publicfiles/js/receiver_registration.js | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/htmltemplate/tmpl/receiver_register.tmpl b/internal/htmltemplate/tmpl/receiver_register.tmpl index 20b0b5e6c..f06ae3772 100644 --- a/internal/htmltemplate/tmpl/receiver_register.tmpl +++ b/internal/htmltemplate/tmpl/receiver_register.tmpl @@ -87,7 +87,7 @@
- +

Enter passcode

diff --git a/internal/serve/publicfiles/js/receiver_registration.js b/internal/serve/publicfiles/js/receiver_registration.js index eb23a3a56..3ea98525c 100644 --- a/internal/serve/publicfiles/js/receiver_registration.js +++ b/internal/serve/publicfiles/js/receiver_registration.js @@ -96,8 +96,8 @@ async function submitPhoneNumber(event) { "#g-recaptcha-response" ); const buttonEls = phoneNumberSectionEl.querySelectorAll("[data-button]"); - const verificationTypeTitle = document.querySelector("label[for='verification']"); - const verificationTypeInput = document.querySelector("#verification"); + const verificationFieldTitle = document.querySelector("label[for='verification']"); + const verificationFieldInput = document.querySelector("#verification"); if (!reCAPTCHATokenEl || !reCAPTCHATokenEl.value) { toggleErrorNotification( @@ -135,20 +135,20 @@ async function submitPhoneNumber(event) { return; } - function showNextPage(verificationType) { - verificationTypeInput.type = "text"; - if(verificationType === "DATE_OF_BIRTH") { - verificationTypeTitle.textContent = "Date_of_birth"; - verificationTypeInput.name = "date_of_birth"; - verificationTypeInput.type = "date"; + function showNextPage(verificationField) { + verificationFieldInput.type = "text"; + if(verificationField === "DATE_OF_BIRTH") { + verificationFieldTitle.textContent = "Date_of_birth"; + verificationFieldInput.name = "date_of_birth"; + verificationFieldInput.type = "date"; } - else if(verificationType === "NATIONAL_ID") { - verificationTypeTitle.textContent = "National_ID"; - verificationTypeInput.name = "national_id"; + else if(verificationField === "NATIONAL_ID") { + verificationFieldTitle.textContent = "National_ID"; + verificationFieldInput.name = "national_id"; } - else if(verificationType === "PIN") { - verificationTypeTitle.textContent = "Pin"; - verificationTypeInput.name = "pin"; + else if(verificationField === "PIN") { + verificationFieldTitle.textContent = "Pin"; + verificationFieldInput.name = "pin"; } phoneNumberSectionEl.style.display = "none"; From da8dddf6d8e0492c29d086fa7a7531f1590018f3 Mon Sep 17 00:00:00 2001 From: Erica Date: Thu, 14 Dec 2023 16:04:09 -0800 Subject: [PATCH 8/9] fix --- internal/serve/publicfiles/js/receiver_registration.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/serve/publicfiles/js/receiver_registration.js b/internal/serve/publicfiles/js/receiver_registration.js index 3ea98525c..2ff2ae57c 100644 --- a/internal/serve/publicfiles/js/receiver_registration.js +++ b/internal/serve/publicfiles/js/receiver_registration.js @@ -138,13 +138,13 @@ async function submitPhoneNumber(event) { function showNextPage(verificationField) { verificationFieldInput.type = "text"; if(verificationField === "DATE_OF_BIRTH") { - verificationFieldTitle.textContent = "Date_of_birth"; + verificationFieldTitle.textContent = "Date of birth"; verificationFieldInput.name = "date_of_birth"; verificationFieldInput.type = "date"; } - else if(verificationField === "NATIONAL_ID") { - verificationFieldTitle.textContent = "National_ID"; - verificationFieldInput.name = "national_id"; + else if(verificationField === "NATIONAL_ID_NUMBER") { + verificationFieldTitle.textContent = "National ID number"; + verificationFieldInput.name = "national_id_number"; } else if(verificationField === "PIN") { verificationFieldTitle.textContent = "Pin"; From ccf965e22a6bf312776050a82ee530f39151f03a Mon Sep 17 00:00:00 2001 From: Erica Date: Fri, 15 Dec 2023 14:52:21 -0800 Subject: [PATCH 9/9] add backend verification --- internal/data/receiver_verification.go | 41 ++++++++--------- internal/data/receiver_verification_test.go | 10 ++-- .../receiver_registration_validator.go | 13 +++++- .../receiver_registration_validator_test.go | 46 +++++++++++++++++++ 4 files changed, 82 insertions(+), 28 deletions(-) diff --git a/internal/data/receiver_verification.go b/internal/data/receiver_verification.go index 454f716d5..f660cca32 100644 --- a/internal/data/receiver_verification.go +++ b/internal/data/receiver_verification.go @@ -2,6 +2,8 @@ package data import ( "context" + "database/sql" + "errors" "fmt" "strings" "time" @@ -32,7 +34,6 @@ type ReceiverVerificationInsert struct { ReceiverID string `db:"receiver_id"` VerificationField VerificationField `db:"verification_field"` VerificationValue string `db:"hashed_value"` - UpdatedAt *time.Time `db:"updated_at"` } const MaxAttemptsAllowed = 15 @@ -96,28 +97,29 @@ func (m *ReceiverVerificationModel) GetAllByReceiverId(ctx context.Context, sqlE // GetLatestByPhoneNumber returns the latest updated receiver verification for some receiver that is associated with a phone number. func (m *ReceiverVerificationModel) GetLatestByPhoneNumber(ctx context.Context, phoneNumber string) (*ReceiverVerification, error) { - receiverVerifications := []ReceiverVerification{} + receiverVerification := ReceiverVerification{} query := ` SELECT - rv.* + rv.* FROM - receiver_verifications rv + receiver_verifications rv JOIN receivers r ON rv.receiver_id = r.id - WHERE r.phone_number = $1 + WHERE + r.phone_number = $1 ORDER BY - rv.updated_at DESC + rv.updated_at DESC + LIMIT 1 ` - err := m.dbConnectionPool.SelectContext(ctx, &receiverVerifications, query, phoneNumber) + err := m.dbConnectionPool.GetContext(ctx, &receiverVerification, query, phoneNumber) if err != nil { - return nil, fmt.Errorf("error querying receiver verifications for phone number %s: %w", phoneNumber, err) - } - - if len(receiverVerifications) == 0 { - return nil, fmt.Errorf("cannot query any receiver verifications for phone number %s: %w", phoneNumber, err) + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrRecordNotFound + } + return nil, fmt.Errorf("fetching receiver verifications for phone number %s: %w", phoneNumber, err) } - return &receiverVerifications[0], nil + return &receiverVerification, nil } // Insert inserts a new receiver verification @@ -135,18 +137,11 @@ func (m *ReceiverVerificationModel) Insert(ctx context.Context, sqlExec db.SQLEx INSERT INTO receiver_verifications ( receiver_id, verification_field, - hashed_value%s - ) VALUES ($1, $2, $3%s) + hashed_value + ) VALUES ($1, $2, $3) ` - if verificationInsert.UpdatedAt != nil { - query = fmt.Sprintf(query, ",\nupdated_at\n", ", $4") - _, err = sqlExec.ExecContext(ctx, query, verificationInsert.ReceiverID, verificationInsert.VerificationField, hashedValue, *verificationInsert.UpdatedAt) - } else { - query = fmt.Sprintf(query, "", "") - _, err = sqlExec.ExecContext(ctx, query, verificationInsert.ReceiverID, verificationInsert.VerificationField, hashedValue) - } - + _, err = sqlExec.ExecContext(ctx, query, verificationInsert.ReceiverID, verificationInsert.VerificationField, hashedValue) if err != nil { return "", fmt.Errorf("error inserting receiver verification: %w", err) } diff --git a/internal/data/receiver_verification_test.go b/internal/data/receiver_verification_test.go index 9f955d3c3..c68b8a636 100644 --- a/internal/data/receiver_verification_test.go +++ b/internal/data/receiver_verification_test.go @@ -148,18 +148,20 @@ func Test_ReceiverVerificationModel_GetReceiverVerificationByReceiverId(t *testi t.Run("returns the latest receiver verification for a list of receiver verifications", func(t *testing.T) { earlierTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) - CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ + verification1 := CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ ReceiverID: receiver.ID, VerificationField: VerificationFieldDateOfBirth, VerificationValue: "1990-01-01", - UpdatedAt: &earlierTime, }) - CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ + verification1.UpdatedAt = earlierTime + + verification2 := CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ ReceiverID: receiver.ID, VerificationField: VerificationFieldPin, VerificationValue: "1234", - UpdatedAt: &earlierTime, }) + verification2.UpdatedAt = earlierTime + verification3 := CreateReceiverVerificationFixture(t, ctx, dbConnectionPool, ReceiverVerificationInsert{ ReceiverID: receiver.ID, VerificationField: VerificationFieldNationalID, diff --git a/internal/serve/validators/receiver_registration_validator.go b/internal/serve/validators/receiver_registration_validator.go index b7b2b5e94..e0a57904c 100644 --- a/internal/serve/validators/receiver_registration_validator.go +++ b/internal/serve/validators/receiver_registration_validator.go @@ -42,8 +42,19 @@ func (rv *ReceiverRegistrationValidator) ValidateReceiver(receiverInfo *data.Rec // validate verification field // date of birth with format 2006-01-02 if vt == data.VerificationFieldDateOfBirth { - _, err := time.Parse("2006-01-02", verification) + dob, err := time.Parse("2006-01-02", verification) rv.CheckError(err, "verification", "invalid date of birth format. Correct format: 1990-01-01") + + // check if date of birth is in the past + rv.Check(dob.Before(time.Now()), "verification", "date of birth cannot be in the future") + } else if vt == data.VerificationFieldPin { + if len(verification) < VERIFICATION_FIELD_PIN_MIN_LENGTH || len(verification) > VERIFICATION_FIELD_PIN_MAX_LENGTH { + rv.addError("verification", "invalid pin. Cannot have less than 4 or more than 8 characters in pin") + } + } else if vt == data.VerificationFieldNationalID { + if len(verification) > VERIFICATION_FIELD_MAX_ID_LENGTH { + rv.addError("verification", "invalid national id. Cannot have more than 50 characters in national id") + } } else { // TODO: validate other VerificationField types. log.Warnf("Verification type %v is not being validated for ValidateReceiver", vt) diff --git a/internal/serve/validators/receiver_registration_validator_test.go b/internal/serve/validators/receiver_registration_validator_test.go index 2cb6257d3..cebce9c71 100644 --- a/internal/serve/validators/receiver_registration_validator_test.go +++ b/internal/serve/validators/receiver_registration_validator_test.go @@ -83,6 +83,36 @@ func Test_ReceiverRegistrationValidator_ValidateReceiver(t *testing.T) { assert.Equal(t, "invalid date of birth format. Correct format: 1990-01-01", validator.Errors["verification"]) }) + t.Run("Invalid pin", func(t *testing.T) { + validator := NewReceiverRegistrationValidator() + + receiverInfo := data.ReceiverRegistrationRequest{ + PhoneNumber: "+380445555555", + OTP: "123456", + VerificationValue: "ABCDE1234", + VerificationType: "PIN", + } + validator.ValidateReceiver(&receiverInfo) + + assert.Equal(t, 1, len(validator.Errors)) + assert.Equal(t, "invalid pin. Cannot have less than 4 or more than 8 characters in pin", validator.Errors["verification"]) + }) + + t.Run("Invalid national ID number", func(t *testing.T) { + validator := NewReceiverRegistrationValidator() + + receiverInfo := data.ReceiverRegistrationRequest{ + PhoneNumber: "+380445555555", + OTP: "123456", + VerificationValue: "6UZMB56FWTKV4U0PJ21TBR6VOQVYSGIMZG2HW2S0L7EK5K83W78XXXXX", + VerificationType: "NATIONAL_ID_NUMBER", + } + validator.ValidateReceiver(&receiverInfo) + + assert.Equal(t, 1, len(validator.Errors)) + assert.Equal(t, "invalid national id. Cannot have more than 50 characters in national id", validator.Errors["verification"]) + }) + t.Run("Valid receiver values", func(t *testing.T) { validator := NewReceiverRegistrationValidator() @@ -99,6 +129,22 @@ func Test_ReceiverRegistrationValidator_ValidateReceiver(t *testing.T) { assert.Equal(t, "123456", receiverInfo.OTP) assert.Equal(t, "1990-01-01", receiverInfo.VerificationValue) assert.Equal(t, data.VerificationField("DATE_OF_BIRTH"), receiverInfo.VerificationType) + + receiverInfo.VerificationValue = "1234" + receiverInfo.VerificationType = "pin" + validator.ValidateReceiver(&receiverInfo) + + assert.Equal(t, 0, len(validator.Errors)) + assert.Equal(t, "1234", receiverInfo.VerificationValue) + assert.Equal(t, data.VerificationField("PIN"), receiverInfo.VerificationType) + + receiverInfo.VerificationValue = "NATIONALIDNUMBER123" + receiverInfo.VerificationType = "national_id_number" + validator.ValidateReceiver(&receiverInfo) + + assert.Equal(t, 0, len(validator.Errors)) + assert.Equal(t, "NATIONALIDNUMBER123", receiverInfo.VerificationValue) + assert.Equal(t, data.VerificationField("NATIONAL_ID_NUMBER"), receiverInfo.VerificationType) }) }