diff --git a/common/db.go b/common/db.go index a5cf1c1..5e2fc4c 100644 --- a/common/db.go +++ b/common/db.go @@ -7,7 +7,7 @@ import ( "git.defalsify.org/vise.git/logging" ) -// DataType is a subprefix value used in association with vise/db.DATATYPE_USERDATA. +// DataType is a subprefix value used in association with vise/db.DATATYPE_USERDATA. // // All keys are used only within the context of a single account. Unless otherwise specified, the user context is the session id. // @@ -55,6 +55,8 @@ const ( DATA_ACTIVE_DECIMAL // EVM address of the currently active voucher DATA_ACTIVE_ADDRESS + //Holds count of the number of incorrect PIN attempts + DATA_INCORRECT_PIN_ATTEMPTS ) const ( diff --git a/common/pin.go b/common/pin.go index 6db9d15..13f21b3 100644 --- a/common/pin.go +++ b/common/pin.go @@ -6,9 +6,13 @@ import ( "golang.org/x/crypto/bcrypt" ) -// Define the regex pattern as a constant const ( + // Define the regex pattern as a constant pinPattern = `^\d{4}$` + + //Allowed incorrect PIN attempts + AllowedPINAttempts = uint8(3) + ) // checks whether the given input is a 4 digit number diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/application/menuhandler.go similarity index 94% rename from internal/handlers/ussd/menuhandler.go rename to internal/handlers/application/menuhandler.go index dfdbd02..52b42b3 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/application/menuhandler.go @@ -1,4 +1,4 @@ -package ussd +package application import ( "bytes" @@ -734,11 +734,23 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res if h.st.MatchFlag(flag_account_authorized, false) { res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) + err := h.resetIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err + } } else { res.FlagSet = append(res.FlagSet, flag_allow_update) res.FlagReset = append(res.FlagReset, flag_account_authorized) + err := h.resetIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err + } } } else { + err := h.incrementIncorrectPINAttempts(ctx, sessionId) + if err != nil { + return res, err + } res.FlagSet = append(res.FlagSet, flag_incorrect_pin) res.FlagReset = append(res.FlagReset, flag_account_authorized) return res, nil @@ -752,8 +764,34 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res // ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + store := h.userdataStore + flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") + flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if !db.IsNotFound(err) { + return res, err + } + } + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + remainingPINAttempts := common.AllowedPINAttempts - uint8(pinAttemptsValue) + if remainingPINAttempts == 0 { + res.FlagSet = append(res.FlagSet, flag_account_blocked) + return res, nil + } + if remainingPINAttempts < common.AllowedPINAttempts { + res.Content = strconv.Itoa(int(remainingPINAttempts)) + } + return res, nil } @@ -835,11 +873,21 @@ func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) ( l := gotext.NewLocale(translationDir, code) l.AddDomain("default") - res.Content = l.Get("For more help,please call: 0757628885") + res.Content = l.Get("For more help, please call: 0757628885") res.FlagReset = append(res.FlagReset, flag_account_authorized) return res, nil } +// ShowBlockedAccount displays a message after an account has been blocked and how to reach support. +func (h *Handlers) ShowBlockedAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + res.Content = l.Get("Your account has been locked. For help on how to unblock your account, contact support at: 0757628885") + return res, nil +} + // VerifyYob verifies the length of the given input. func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -2075,3 +2123,53 @@ func (h *Handlers) UpdateAllProfileItems(ctx context.Context, sym string, input } return res, nil } + +// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts +func (h *Handlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error { + var pinAttemptsCount uint8 + store := h.userdataStore + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if db.IsNotFound(err) { + //First time Wrong PIN attempt: initialize with a count of 1 + pinAttemptsCount = 1 + err = store.WriteEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", common.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err) + return err + } + return nil + } + } + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + pinAttemptsCount = uint8(pinAttemptsValue) + 1 + + err = store.WriteEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", common.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err) + return err + } + return nil +} + +// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry +func (h *Handlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error { + store := h.userdataStore + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if db.IsNotFound(err) { + return nil + } + return err + } + currentWrongPinAttemptsCount, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + if currentWrongPinAttemptsCount <= uint64(common.AllowedPINAttempts) { + err = store.WriteEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", common.DATA_INCORRECT_PIN_ATTEMPTS, "value", common.AllowedPINAttempts, "error", err) + return err + } + } + return nil +} diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/application/menuhandler_test.go similarity index 95% rename from internal/handlers/ussd/menuhandler_test.go rename to internal/handlers/application/menuhandler_test.go index 914dffc..bb6230e 100644 --- a/internal/handlers/ussd/menuhandler_test.go +++ b/internal/handlers/application/menuhandler_test.go @@ -1,10 +1,11 @@ -package ussd +package application import ( "context" "fmt" "log" "path" + "strconv" "strings" "testing" @@ -907,37 +908,79 @@ func TestResetAccountAuthorized(t *testing.T) { } func TestIncorrectPinReset(t *testing.T) { + sessionId := "session123" + ctx, store := InitializeTestStore(t) fm, err := NewFlagManager(flagsPath) + if err != nil { log.Fatal(err) } flag_incorrect_pin, _ := fm.parser.GetFlag("flag_incorrect_pin") + flag_account_blocked, _ := fm.parser.GetFlag("flag_account_blocked") + + ctx = context.WithValue(ctx, "SessionId", sessionId) // Define test cases tests := []struct { name string input []byte + attempts uint8 expectedResult resource.Result }{ { - name: "Test incorrect pin reset", + name: "Test when incorrect PIN attempts is 2", input: []byte(""), expectedResult: resource.Result{ FlagReset: []uint32{flag_incorrect_pin}, + Content: "1", //Expected remaining PIN attempts }, + attempts: 2, + }, + { + name: "Test incorrect pin reset when incorrect PIN attempts is 1", + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_pin}, + Content: "2", //Expected remaining PIN attempts + }, + attempts: 1, + }, + { + name: "Test incorrect pin reset when incorrect PIN attempts is 1", + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_pin}, + Content: "2", //Expected remaining PIN attempts + }, + attempts: 1, + }, + { + name: "Test incorrect pin reset when incorrect PIN attempts is 3(account expected to be blocked)", + input: []byte(""), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_incorrect_pin}, + FlagSet: []uint32{flag_account_blocked}, + }, + attempts: 3, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + + if err := store.WriteEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(tt.attempts)))); err != nil { + t.Fatal(err) + } + // Create the Handlers instance with the mock flag manager h := &Handlers{ - flagManager: fm.parser, + flagManager: fm.parser, + userdataStore: store, } // Call the method - res, err := h.ResetIncorrectPin(context.Background(), "reset_incorrect_pin", tt.input) + res, err := h.ResetIncorrectPin(ctx, "reset_incorrect_pin", tt.input) if err != nil { t.Error(err) } @@ -2190,3 +2233,55 @@ func TestGetVoucherDetails(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expectedResult, res) } + +func TestCountIncorrectPINAttempts(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + attempts := uint8(2) + + h := &Handlers{ + userdataStore: store, + } + err := store.WriteEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(attempts)))) + if err != nil { + t.Logf(err.Error()) + } + err = h.incrementIncorrectPINAttempts(ctx, sessionId) + if err != nil { + t.Logf(err.Error()) + } + + attemptsAfterCount, err := store.ReadEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + t.Logf(err.Error()) + } + pinAttemptsValue, _ := strconv.ParseUint(string(attemptsAfterCount), 0, 64) + pinAttemptsCount := uint8(pinAttemptsValue) + expectedAttempts := attempts + 1 + assert.Equal(t, pinAttemptsCount, expectedAttempts) + +} + +func TestResetIncorrectPINAttempts(t *testing.T) { + ctx, store := InitializeTestStore(t) + sessionId := "session123" + ctx = context.WithValue(ctx, "SessionId", sessionId) + + err := store.WriteEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("2"))) + if err != nil { + t.Logf(err.Error()) + } + + h := &Handlers{ + userdataStore: store, + } + h.resetIncorrectPINAttempts(ctx, sessionId) + incorrectAttempts, err := store.ReadEntry(ctx, sessionId, common.DATA_INCORRECT_PIN_ATTEMPTS) + + if err != nil { + t.Logf(err.Error()) + } + assert.Equal(t, "0", string(incorrectAttempts)) + +} diff --git a/internal/handlers/base.go b/internal/handlers/base.go index 755cca4..6c77f49 100644 --- a/internal/handlers/base.go +++ b/internal/handlers/base.go @@ -6,46 +6,46 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" - "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + "git.grassecon.net/urdt/ussd/internal/handlers/application" "git.grassecon.net/urdt/ussd/internal/storage" ) type BaseSessionHandler struct { cfgTemplate engine.Config - rp RequestParser - rs resource.Resource - hn *ussd.Handlers - provider storage.StorageProvider + rp RequestParser + rs resource.Resource + hn *application.Handlers + provider storage.StorageProvider } -func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *BaseSessionHandler { +func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *application.Handlers) *BaseSessionHandler { return &BaseSessionHandler{ cfgTemplate: cfg, - rs: rs, - hn: hn, - rp: rp, - provider: storage.NewSimpleStorageProvider(stateDb, userdataDb), + rs: rs, + hn: hn, + rp: rp, + provider: storage.NewSimpleStorageProvider(stateDb, userdataDb), } } -func(f* BaseSessionHandler) Shutdown() { +func (f *BaseSessionHandler) Shutdown() { err := f.provider.Close() if err != nil { logg.Errorf("handler shutdown error", "err", err) } } -func(f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { +func (f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { en := engine.NewEngine(cfg, rs) en = en.WithPersister(pr) return en } -func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) { +func (f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) { var r bool var err error var ok bool - + logg.InfoCtxf(rqs.Ctx, "new request", "data", rqs) rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) @@ -84,25 +84,25 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) return rqs, err } - rqs.Continue = r + rqs.Continue = r return rqs, nil } -func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) { +func (f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) { var err error _, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer) return rqs, err } -func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) { +func (f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) { defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) return rqs, rqs.Engine.Finish() } -func(f *BaseSessionHandler) GetConfig() engine.Config { +func (f *BaseSessionHandler) GetConfig() engine.Config { return f.cfgTemplate } -func(f *BaseSessionHandler) GetRequestParser() RequestParser { +func (f *BaseSessionHandler) GetRequestParser() RequestParser { return f.rp } diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 1da28c3..eb97cab 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -10,13 +10,13 @@ import ( "git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/resource" - "git.grassecon.net/urdt/ussd/internal/handlers/ussd" + "git.grassecon.net/urdt/ussd/internal/handlers/application" "git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/remote" ) type HandlerService interface { - GetHandler() (*ussd.Handlers, error) + GetHandler() (*application.Handlers, error) } func getParser(fp string, debug bool) (*asm.FlagParser, error) { @@ -64,72 +64,72 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) { ls.UserdataStore = db } -func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) { +func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*application.Handlers, error) { replaceSeparatorFunc := func(input string) string { return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator) } - ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc) + appHandlers, err := application.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc) if err != nil { return nil, err } - ussdHandlers = ussdHandlers.WithPersister(ls.Pe) - ls.DbRs.AddLocalFunc("set_language", ussdHandlers.SetLanguage) - ls.DbRs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) - ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin) - ls.DbRs.AddLocalFunc("verify_create_pin", ussdHandlers.VerifyCreatePin) - ls.DbRs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) - ls.DbRs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus) - ls.DbRs.AddLocalFunc("authorize_account", ussdHandlers.Authorize) - ls.DbRs.AddLocalFunc("quit", ussdHandlers.Quit) - ls.DbRs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) - ls.DbRs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) - ls.DbRs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) - ls.DbRs.AddLocalFunc("invite_valid_recipient", ussdHandlers.InviteValidRecipient) - ls.DbRs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) - ls.DbRs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) - ls.DbRs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) - ls.DbRs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient) - ls.DbRs.AddLocalFunc("get_sender", ussdHandlers.GetSender) - ls.DbRs.AddLocalFunc("get_amount", ussdHandlers.GetAmount) - ls.DbRs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin) - ls.DbRs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname) - ls.DbRs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname) - ls.DbRs.AddLocalFunc("save_gender", ussdHandlers.SaveGender) - ls.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) - ls.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) - ls.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) - ls.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) - ls.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) - ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) - ls.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) - ls.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) - ls.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) - ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin) - ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange) - ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp) - ls.DbRs.AddLocalFunc("fetch_community_balance", ussdHandlers.FetchCommunityBalance) - ls.DbRs.AddLocalFunc("set_default_voucher", ussdHandlers.SetDefaultVoucher) - ls.DbRs.AddLocalFunc("check_vouchers", ussdHandlers.CheckVouchers) - ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) - ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher) - ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) - ls.DbRs.AddLocalFunc("get_voucher_details", ussdHandlers.GetVoucherDetails) - ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin) - ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckBlockedNumPinMisMatch) - ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber) - ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber) - ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber) - ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin) - ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin) - ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo) - ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions) - ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList) - ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement) - ls.DbRs.AddLocalFunc("update_all_profile_items", ussdHandlers.UpdateAllProfileItems) - ls.DbRs.AddLocalFunc("set_back", ussdHandlers.SetBack) + appHandlers = appHandlers.WithPersister(ls.Pe) + ls.DbRs.AddLocalFunc("set_language", appHandlers.SetLanguage) + ls.DbRs.AddLocalFunc("create_account", appHandlers.CreateAccount) + ls.DbRs.AddLocalFunc("save_temporary_pin", appHandlers.SaveTemporaryPin) + ls.DbRs.AddLocalFunc("verify_create_pin", appHandlers.VerifyCreatePin) + ls.DbRs.AddLocalFunc("check_identifier", appHandlers.CheckIdentifier) + ls.DbRs.AddLocalFunc("check_account_status", appHandlers.CheckAccountStatus) + ls.DbRs.AddLocalFunc("authorize_account", appHandlers.Authorize) + ls.DbRs.AddLocalFunc("quit", appHandlers.Quit) + ls.DbRs.AddLocalFunc("check_balance", appHandlers.CheckBalance) + ls.DbRs.AddLocalFunc("validate_recipient", appHandlers.ValidateRecipient) + ls.DbRs.AddLocalFunc("transaction_reset", appHandlers.TransactionReset) + ls.DbRs.AddLocalFunc("invite_valid_recipient", appHandlers.InviteValidRecipient) + ls.DbRs.AddLocalFunc("max_amount", appHandlers.MaxAmount) + ls.DbRs.AddLocalFunc("validate_amount", appHandlers.ValidateAmount) + ls.DbRs.AddLocalFunc("reset_transaction_amount", appHandlers.ResetTransactionAmount) + ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient) + ls.DbRs.AddLocalFunc("get_sender", appHandlers.GetSender) + ls.DbRs.AddLocalFunc("get_amount", appHandlers.GetAmount) + ls.DbRs.AddLocalFunc("reset_incorrect", appHandlers.ResetIncorrectPin) + ls.DbRs.AddLocalFunc("save_firstname", appHandlers.SaveFirstname) + ls.DbRs.AddLocalFunc("save_familyname", appHandlers.SaveFamilyname) + ls.DbRs.AddLocalFunc("save_gender", appHandlers.SaveGender) + ls.DbRs.AddLocalFunc("save_location", appHandlers.SaveLocation) + ls.DbRs.AddLocalFunc("save_yob", appHandlers.SaveYob) + ls.DbRs.AddLocalFunc("save_offerings", appHandlers.SaveOfferings) + ls.DbRs.AddLocalFunc("reset_account_authorized", appHandlers.ResetAccountAuthorized) + ls.DbRs.AddLocalFunc("reset_allow_update", appHandlers.ResetAllowUpdate) + ls.DbRs.AddLocalFunc("get_profile_info", appHandlers.GetProfileInfo) + ls.DbRs.AddLocalFunc("verify_yob", appHandlers.VerifyYob) + ls.DbRs.AddLocalFunc("reset_incorrect_date_format", appHandlers.ResetIncorrectYob) + ls.DbRs.AddLocalFunc("initiate_transaction", appHandlers.InitiateTransaction) + ls.DbRs.AddLocalFunc("verify_new_pin", appHandlers.VerifyNewPin) + ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange) + ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp) + ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance) + ls.DbRs.AddLocalFunc("set_default_voucher", appHandlers.SetDefaultVoucher) + ls.DbRs.AddLocalFunc("check_vouchers", appHandlers.CheckVouchers) + ls.DbRs.AddLocalFunc("get_vouchers", appHandlers.GetVoucherList) + ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher) + ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher) + ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails) + ls.DbRs.AddLocalFunc("reset_valid_pin", appHandlers.ResetValidPin) + ls.DbRs.AddLocalFunc("check_pin_mismatch", appHandlers.CheckBlockedNumPinMisMatch) + ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber) + ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber) + ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber) + ls.DbRs.AddLocalFunc("reset_others_pin", appHandlers.ResetOthersPin) + ls.DbRs.AddLocalFunc("save_others_temporary_pin", appHandlers.SaveOthersTemporaryPin) + ls.DbRs.AddLocalFunc("get_current_profile_info", appHandlers.GetCurrentProfileInfo) + ls.DbRs.AddLocalFunc("check_transactions", appHandlers.CheckTransactions) + ls.DbRs.AddLocalFunc("get_transactions", appHandlers.GetTransactionsList) + ls.DbRs.AddLocalFunc("view_statement", appHandlers.ViewTransactionStatement) + ls.DbRs.AddLocalFunc("update_all_profile_items", appHandlers.UpdateAllProfileItems) + ls.DbRs.AddLocalFunc("set_back", appHandlers.SetBack) - return ussdHandlers, nil + return appHandlers, nil } // TODO: enable setting of sessionId on engine init time diff --git a/internal/http/at/parse.go b/internal/http/at/parse.go index 5f27d50..76e84e7 100644 --- a/internal/http/at/parse.go +++ b/internal/http/at/parse.go @@ -81,7 +81,8 @@ func (arp *ATRequestParser) GetInput(rq any) ([]byte, error) { return nil, fmt.Errorf("no input found") } - return []byte(parts[len(parts)-1]), nil + trimmedInput := strings.TrimSpace(parts[len(parts)-1]) + return []byte(trimmedInput), nil } func parseQueryParams(query string) map[string]string { diff --git a/menutraversal_test/group_test.json b/menutraversal_test/group_test.json index f35beb9..0ffb49f 100644 --- a/menutraversal_test/group_test.json +++ b/menutraversal_test/group_test.json @@ -54,7 +54,7 @@ }, { "input": "1235", - "expectedContent": "Incorrect PIN\n1:Retry\n9:Quit" + "expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit" }, { "input": "1", @@ -95,7 +95,7 @@ }, { "input": "1235", - "expectedContent": "Incorrect PIN\n1:Retry\n9:Quit" + "expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit" }, { "input": "1", @@ -107,8 +107,7 @@ }, { "input": "0", - "expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back" - + "expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back" }, { "input": "0", @@ -141,7 +140,7 @@ }, { "input": "1235", - "expectedContent": "Incorrect PIN\n1:Retry\n9:Quit" + "expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit" }, { "input": "1", @@ -153,8 +152,7 @@ }, { "input": "0", - "expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back" - + "expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back" }, { "input": "0", @@ -195,7 +193,7 @@ }, { "input": "1", - "expectedContent": "Enter your year of birth\n0:Back" + "expectedContent": "Enter your year of birth\n0:Back" }, { "input": "1940", @@ -258,7 +256,6 @@ "input": "0", "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" } - ] }, { @@ -443,10 +440,4 @@ ] } ] -} - - - - - - +} \ No newline at end of file diff --git a/services/registration/blocked_account.vis b/services/registration/blocked_account.vis new file mode 100644 index 0000000..d8adab2 --- /dev/null +++ b/services/registration/blocked_account.vis @@ -0,0 +1,2 @@ +LOAD show_blocked_account 0 +HALT diff --git a/services/registration/incorrect_pin b/services/registration/incorrect_pin index 7fcf610..13a9562 100644 --- a/services/registration/incorrect_pin +++ b/services/registration/incorrect_pin @@ -1 +1 @@ -Incorrect PIN \ No newline at end of file +Incorrect PIN. You have: {{.reset_incorrect}} remaining attempt(s). \ No newline at end of file diff --git a/services/registration/incorrect_pin.vis b/services/registration/incorrect_pin.vis index 844f3d6..167364a 100644 --- a/services/registration/incorrect_pin.vis +++ b/services/registration/incorrect_pin.vis @@ -1,5 +1,7 @@ LOAD reset_incorrect 0 RELOAD reset_incorrect +MAP reset_incorrect +CATCH blocked_account flag_account_blocked 1 MOUT retry 1 MOUT quit 9 HALT diff --git a/services/registration/incorrect_pin_swa b/services/registration/incorrect_pin_swa index 34a0b28..ed22beb 100644 --- a/services/registration/incorrect_pin_swa +++ b/services/registration/incorrect_pin_swa @@ -1 +1 @@ -PIN ulioeka sio sahihi \ No newline at end of file +PIN ulioeka sio sahihi, una majaribio: {{.reset_incorrect}} yaliyobaki \ No newline at end of file diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po index 4bf876b..6155063 100644 --- a/services/registration/locale/swa/default.po +++ b/services/registration/locale/swa/default.po @@ -7,8 +7,11 @@ msgstr "Ombi lako limetumwa. %s atapokea %s %s kutoka kwa %s." msgid "Thank you for using Sarafu. Goodbye!" msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!" -msgid "For more help,please call: 0757628885" -msgstr "Kwa usaidizi zaidi,piga: 0757628885" +msgid "For more help, please call: 0757628885" +msgstr "Kwa usaidizi zaidi, piga: 0757628885" + +msgid "Your account has been locked. For help on how to unblock your account, contact support at: 0757628885" +msgstr "Akaunti yako imefungwa. Kwa usaidizi wa jinsi ya kufungua akaunti yako, wasiliana na usaidizi kwa: 0757628885" msgid "Balance: %s\n" msgstr "Salio: %s\n" diff --git a/services/registration/pp.csv b/services/registration/pp.csv index 26a8833..aa1eb05 100644 --- a/services/registration/pp.csv +++ b/services/registration/pp.csv @@ -28,3 +28,5 @@ flag,flag_gender_set,34,this is set when the gender of the profile is set flag,flag_location_set,35,this is set when the location of the profile is set flag,flag_offerings_set,36,this is set when the offerings of the profile is set flag,flag_back_set,37,this is set when it is a back navigation +flag,flag_account_blocked,38,this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded + diff --git a/services/registration/root.vis b/services/registration/root.vis index 02ef9e9..102e6e5 100644 --- a/services/registration/root.vis +++ b/services/registration/root.vis @@ -1,3 +1,4 @@ +CATCH blocked_account flag_account_blocked 1 CATCH select_language flag_language_set 0 CATCH terms flag_account_created 0 LOAD check_account_status 0