diff --git a/Dockerfile b/Dockerfile index 60fa95a..7c09597 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN make VISE_PATH=/build/go-vise -B WORKDIR /build/sarafu-vise RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM" RUN go mod download -RUN go build -tags logwarn,online -o sarafu-at -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go +RUN go build -tags logdebug,online -o sarafu-at -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go FROM debian:bookworm-slim diff --git a/cmd/africastalking/main.go b/cmd/africastalking/main.go index 3f7e372..2f34b56 100644 --- a/cmd/africastalking/main.go +++ b/cmd/africastalking/main.go @@ -44,6 +44,7 @@ func main() { var err error var gettextDir string var langs args.LangVar + var logDbConnStr string flag.BoolVar(&engineDebug, "d", false, "use engine debug output") flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") @@ -55,6 +56,7 @@ func main() { flag.UintVar(&port, "p", config.Port(), "http port") flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory") flag.Var(&langs, "language", "add symbol resolution for language") + flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string") flag.Parse() config.Apply(override) @@ -100,6 +102,11 @@ func main() { fmt.Fprintf(os.Stderr, "userdatadb: %v\n", err) os.Exit(1) } + logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data") + if err != nil { + fmt.Fprintf(os.Stderr, "get log db error: %v\n", err) + os.Exit(1) + } dbResource, ok := rs.(*resource.DbResource) if !ok { @@ -113,6 +120,7 @@ func main() { os.Exit(1) } lhs.SetDataStore(&userdataStore) + lhs.SetLogDb(&logdb) if err != nil { fmt.Fprintf(os.Stderr, "setdatastore: %v\n", err) os.Exit(1) diff --git a/cmd/async/main.go b/cmd/async/main.go index 9a064a6..5bb6816 100644 --- a/cmd/async/main.go +++ b/cmd/async/main.go @@ -56,6 +56,7 @@ func main() { var err error var gettextDir string var langs args.LangVar + var logDbConnStr string flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") @@ -69,6 +70,7 @@ func main() { flag.UintVar(&port, "p", config.Port(), "http port") flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory") flag.Var(&langs, "language", "add symbol resolution for language") + flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string") flag.Parse() config.Apply(override) @@ -120,6 +122,12 @@ func main() { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } + + logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data") + if err != nil { + fmt.Fprintf(os.Stderr, "get log db error: %v\n", err) + os.Exit(1) + } //defer userdataStore.Close(ctx) dbResource, ok := rs.(*resource.DbResource) @@ -129,6 +137,7 @@ func main() { lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs) lhs.SetDataStore(&userdataStore) + lhs.SetLogDb(&logdb) accountService := services.New(ctx, menuStorageService) hl, err := lhs.GetHandler(accountService) diff --git a/cmd/http/main.go b/cmd/http/main.go index 13ef408..eed729a 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -43,6 +43,7 @@ func main() { var err error var gettextDir string var langs args.LangVar + var logDbConnStr string flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string") @@ -55,6 +56,7 @@ func main() { flag.UintVar(&port, "p", config.Port(), "http port") flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory") flag.Var(&langs, "language", "add symbol resolution for language") + flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string") flag.Parse() config.Apply(override) @@ -103,6 +105,12 @@ func main() { os.Exit(1) } + logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data") + if err != nil { + fmt.Fprintf(os.Stderr, "get log db error: %v\n", err) + os.Exit(1) + } + dbResource, ok := rs.(*resource.DbResource) if !ok { os.Exit(1) @@ -110,6 +118,7 @@ func main() { lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs) lhs.SetDataStore(&userdataStore) + lhs.SetLogDb(&logdb) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) diff --git a/cmd/main.go b/cmd/main.go index a18bfab..1b40e32 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -36,6 +36,7 @@ func main() { var err error var gettextDir string var langs args.LangVar + var logDbConnStr string flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") @@ -46,6 +47,7 @@ func main() { flag.UintVar(&size, "s", 160, "max size of output") flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory") flag.Var(&langs, "language", "add symbol resolution for language") + flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string") flag.Parse() config.Apply(override) @@ -110,6 +112,12 @@ func main() { os.Exit(1) } + logdb, err := menuStorageService.GetLogDb(ctx, userdatastore, logDbConnStr, "user-data") + if err != nil { + fmt.Fprintf(os.Stderr, "get log db error: %v\n", err) + os.Exit(1) + } + dbResource, ok := rs.(*resource.DbResource) if !ok { fmt.Fprintf(os.Stderr, "get dbresource error: %v\n", err) @@ -118,6 +126,7 @@ func main() { lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs) lhs.SetDataStore(&userdatastore) + lhs.SetLogDb(&logdb) lhs.SetPersister(pe) if err != nil { fmt.Fprintf(os.Stderr, "localhandler service error: %v\n", err) diff --git a/go.mod b/go.mod index 1b06b27..31b4dc1 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise go 1.23.4 require ( - git.defalsify.org/vise.git v0.3.1 - git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d - git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b - git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2 + git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce + git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e + git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f + git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 github.com/alecthomas/assert/v2 v2.2.2 github.com/gofrs/uuid v4.4.0+incompatible diff --git a/go.sum b/go.sum index 19c67fe..1a2410d 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ -git.defalsify.org/vise.git v0.3.1 h1:A6FhMcur09ft/JzUPGXR+KpA17fltfeBnasyvLMZmq4= -git.defalsify.org/vise.git v0.3.1/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= -git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ= -git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= -git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b h1:xiTpaqWWoF5qcnarY/9ZkT6aVdnKwqztb2gzIahJn4w= -git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8= -git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2 h1:YFztSsexCUgFo6M0tbngRwYdgJd3LQV3RO/Jw09u3+k= -git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2/go.mod h1:6B6ByxXOiRY0NR7K02Bf3fEu7z+2c/6q8PFVNjC5G8w= +git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce h1:Uke2jQ4wG97gQKnTzxPyBGyhosrU1IWnRNFHtKVrmrk= +git.defalsify.org/vise.git v0.3.2-0.20250425131748-8b84f59792ce/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc= +git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f h1:OAHCP3YR1C5h1WFnnEnLs5kn6jTxQHQYWYtQaMZJIMY= +git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250428082711-5d221b8d565f/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8= +git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4= +git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E= git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694/go.mod h1:DpibtYpnT3nG4Kn556hRAkdu4+CtiI/6MbnQHal51mQ= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 26cf13a..7178188 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -29,6 +29,7 @@ import ( "git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/remote" "git.grassecon.net/grassrootseconomics/sarafu-vise/config" + "git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms" "git.grassecon.net/grassrootseconomics/sarafu-vise/profile" "git.grassecon.net/grassrootseconomics/sarafu-vise/store" storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" @@ -77,18 +78,28 @@ type MenuHandlers struct { flagManager *FlagManager accountService remote.AccountService prefixDb storedb.PrefixDb + smsService sms.SmsService + logDb store.LogDb profile *profile.Profile ReplaceSeparatorFunc func(string) string } // NewHandlers creates a new instance of the Handlers struct with the provided dependencies. -func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, accountService remote.AccountService, replaceSeparatorFunc func(string) string) (*MenuHandlers, error) { +func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, logdb db.Db, accountService remote.AccountService, replaceSeparatorFunc func(string) string) (*MenuHandlers, error) { if userdataStore == nil { return nil, fmt.Errorf("cannot create handler with nil userdata store") } userDb := &store.UserDataStore{ Db: userdataStore, } + smsservice := sms.SmsService{ + Accountservice: accountService, + Userdatastore: *userDb, + } + + logDb := store.LogDb{ + Db: logdb, + } // Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix prefix := storedb.ToBytes(db.DATATYPE_USERDATA) @@ -98,7 +109,9 @@ func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, accountService userdataStore: userDb, flagManager: appFlags, accountService: accountService, + smsService: smsservice, prefixDb: prefixDb, + logDb: logDb, profile: &profile.Profile{Max: 6}, ReplaceSeparatorFunc: replaceSeparatorFunc, } @@ -205,11 +218,16 @@ func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId strin storedb.DATA_ACCOUNT_ALIAS: "", } store := h.userdataStore + logdb := h.logDb for key, value := range data { err = store.WriteEntry(ctx, sessionId, key, []byte(value)) if err != nil { return err } + err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write log entry", "key", key, "value", value) + } } publicKeyNormalized, err := hex.NormalizeHex(publicKey) if err != nil { @@ -219,6 +237,12 @@ func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId strin if err != nil { return err } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write log entry", "key", storedb.DATA_PUBLIC_KEY_REVERSE, "value", sessionId) + } + res.FlagSet = append(res.FlagSet, flag_account_created) return nil } @@ -251,6 +275,7 @@ func (h *MenuHandlers) CreateAccount(ctx context.Context, sym string, input []by func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + flag_language_set, _ := h.flagManager.GetFlag("flag_language_set") flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") store := h.userdataStore @@ -262,35 +287,46 @@ func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, inpu _, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) if err != nil { - if !db.IsNotFound(err) { - return res, err + if db.IsNotFound(err) { + // reset major flags + res.FlagReset = append(res.FlagReset, flag_language_set) + res.FlagReset = append(res.FlagReset, flag_account_created) + + return res, nil } + return res, nil } + res.FlagSet = append(res.FlagSet, flag_account_created) return res, nil } -// ResetValidPin resets the flag_valid_pin flag. -func (h *MenuHandlers) ResetValidPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") - res.FlagReset = append(res.FlagReset, flag_valid_pin) - return res, nil -} - -// CheckBlockedStatus resets the account blocked flag if the PIN attempts have been reset by an admin. +// CheckBlockedStatus: +// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset +// 2. resets the account blocked flag if the PIN attempts have been reset by an admin. func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") + flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset") sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } + res.FlagReset = append(res.FlagReset, flag_account_pin_reset) + + selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET) + if err == nil { + pinResetValue, _ := strconv.ParseUint(string(selfPinReset), 0, 64) + if pinResetValue == 1 { + res.FlagSet = append(res.FlagSet, flag_account_pin_reset) + } + } + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) if err != nil { if !db.IsNotFound(err) { @@ -299,7 +335,6 @@ func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input } pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - if pinAttemptsValue == 0 { res.FlagReset = append(res.FlagReset, flag_account_blocked) return res, nil @@ -342,29 +377,6 @@ func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input return res, nil } -// VerifyNewPin checks if a new PIN meets the required format criteria. -func (h *MenuHandlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - res := resource.Result{} - _, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") - if string(input) != "0" { - pinInput := string(input) - // Validate that the PIN is a 4-digit number. - if pin.IsValidPIN(pinInput) { - res.FlagSet = append(res.FlagSet, flag_valid_pin) - } else { - res.FlagReset = append(res.FlagReset, flag_valid_pin) - } - } else { - res.FlagSet = append(res.FlagSet, flag_valid_pin) - } - - return res, nil -} - // SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE, // during the account creation process // and during the change PIN process. @@ -377,15 +389,20 @@ func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input [ return res, fmt.Errorf("missing session") } - flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") - accountPIN := string(input) + flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") - // Validate that the PIN is a 4-digit number. - if !pin.IsValidPIN(accountPIN) { - res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + if string(input) == "0" { return res, nil } - res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + accountPIN := string(input) + + // Validate that the PIN has a valid format. + if !pin.IsValidPIN(accountPIN) { + res.FlagSet = append(res.FlagSet, flag_invalid_pin) + return res, nil + } + res.FlagReset = append(res.FlagReset, flag_invalid_pin) // Hash the PIN hashedPIN, err := pin.HashPIN(string(accountPIN)) @@ -395,93 +412,19 @@ func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input [ } store := h.userdataStore + logdb := h.logDb + err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) return res, err } - return res, nil -} - -// SaveOthersTemporaryPin allows authorized users to set temporary PINs for blocked numbers. -func (h *MenuHandlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var err error - - store := h.userdataStore - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - temporaryPin := string(input) - - // Validate that the input is a 4-digit number. - if !pin.IsValidPIN(temporaryPin) { - return res, nil - } - - // Retrieve the blocked number associated with this session - blockedNumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) if err != nil { - logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) - return res, err + logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) } - // Hash the temporary PIN - hashedPIN, err := pin.HashPIN(string(temporaryPin)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err) - return res, err - } - - // Save the hashed temporary PIN for that blocked number - err = store.WriteEntry(ctx, string(blockedNumber), storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write hashed temporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryPin, "error", err) - return res, err - } - - return res, nil -} - -// CheckBlockedNumPinMisMatch checks if the provided PIN matches a temporary PIN stored for a blocked number. -func (h *MenuHandlers) CheckBlockedNumPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) { - res := resource.Result{} - flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - if string(input) == "0" { - res.FlagReset = append(res.FlagReset, flag_pin_mismatch) - return res, nil - } - - // Get blocked number from storage. - store := h.userdataStore - blockedNumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) - return res, err - } - // Get Hashed temporary PIN for the blocked number. - hashedTemporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), storedb.DATA_TEMPORARY_VALUE) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) - return res, err - } - if len(hashedTemporaryPin) == 0 { - logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - - if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) { - res.FlagReset = append(res.FlagReset, flag_pin_mismatch) - } else { - res.FlagSet = append(res.FlagSet, flag_pin_mismatch) - } return res, nil } @@ -509,6 +452,7 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [ return res, fmt.Errorf("missing session") } flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") + flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset") if string(input) == "0" { res.FlagReset = append(res.FlagReset, flag_pin_mismatch) @@ -516,6 +460,7 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [ } store := h.userdataStore + logdb := h.logDb hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) if err != nil { logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) @@ -540,16 +485,85 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [ return res, err } + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write AccountPIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) + } + + // set the DATA_SELF_PIN_RESET as 0 + err = store.WriteEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET, []byte("0")) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write DATA_SELF_PIN_RESET entry with", "key", storedb.DATA_SELF_PIN_RESET, "self PIN reset value", "0", "error", err) + return res, err + } + res.FlagReset = append(res.FlagReset, flag_account_pin_reset) + + return res, nil +} + +// ValidateBlockedNumber performs validation of phone numbers during the Reset other's PIN. +// It checks phone number format and verifies registration status. +// If valid, it writes the number under DATA_BLOCKED_NUMBER on the admin account +func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + + flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number") + store := h.userdataStore + logdb := h.logDb + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + if string(input) == "0" { + res.FlagReset = append(res.FlagReset, flag_unregistered_number) + return res, nil + } + + blockedNumber := string(input) + formattedNumber, err := phone.FormatPhoneNumber(blockedNumber) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_unregistered_number) + logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err) + return res, nil + } + + _, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) + if err != nil { + if db.IsNotFound(err) { + logg.InfoCtxf(ctx, "Invalid or unregistered number") + res.FlagSet = append(res.FlagSet, flag_unregistered_number) + return res, nil + } else { + logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err) + return res, err + } + } + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) + if err != nil { + return res, nil + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write blocked number log entry", "key", storedb.DATA_BLOCKED_NUMBER, "value", formattedNumber, "error", err) + } + return res, nil } // ResetOthersPin handles the PIN reset process for other users' accounts by: // 1. Retrieving the blocked phone number from the session -// 2. Fetching the hashed temporary PIN associated with that number -// 3. Updating the account PIN with the temporary PIN +// 2. Writing the DATA_SELF_PIN_RESET on the blocked phone number +// 3. Resetting the DATA_INCORRECT_PIN_ATTEMPTS to 0 for the blocked phone number func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + store := h.userdataStore + smsservice := h.smsService + sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") @@ -559,19 +573,11 @@ func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []b logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) return res, err } - hashedTemporaryPin, err := store.ReadEntry(ctx, string(blockedPhonenumber), storedb.DATA_TEMPORARY_VALUE) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read hashedTmporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) - return res, err - } - if len(hashedTemporaryPin) == 0 { - logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") - } - err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + // set the DATA_SELF_PIN_RESET for the account + err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_SELF_PIN_RESET, []byte("1")) if err != nil { - return res, err + return res, nil } err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) @@ -579,7 +585,15 @@ func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []b logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) return res, err } - + blockedPhoneStr := string(blockedPhonenumber) + //Trigger an SMS to inform a user that the blocked account has been reset + if phone.IsValidPhoneNumber(blockedPhoneStr) { + err = smsservice.SendPINResetSMS(ctx, sessionId, blockedPhoneStr) + if err != nil { + logg.DebugCtxf(ctx, "Failed to send PIN reset SMS", "error", err) + return res, nil + } + } return res, nil } @@ -642,65 +656,29 @@ func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, return res, nil } -// ValidateBlockedNumber performs validation of phone numbers, specifically for blocked numbers in the system. -// It checks phone number format and verifies registration status. -func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var err error - - flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number") - store := h.userdataStore - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - if string(input) == "0" { - res.FlagReset = append(res.FlagReset, flag_unregistered_number) - return res, nil - } - - blockedNumber := string(input) - formattedNumber, err := phone.FormatPhoneNumber(blockedNumber) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_unregistered_number) - logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err) - return res, nil - } - - _, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY) - if err != nil { - if db.IsNotFound(err) { - logg.InfoCtxf(ctx, "Invalid or unregistered number") - res.FlagSet = append(res.FlagSet, flag_unregistered_number) - return res, nil - } else { - logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err) - return res, err - } - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber)) - if err != nil { - return res, nil - } - return res, nil -} - // VerifyCreatePin checks whether the confirmation PIN is similar to the temporary PIN // If similar, it sets the USERFLAG_PIN_SET flag and writes the account PIN allowing the user // to access the main menu. func (h *MenuHandlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") - flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") - flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set") - sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } + + flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") + flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") + flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set") + + if string(input) == "0" { + res.FlagReset = append(res.FlagReset, flag_pin_mismatch) + return res, nil + } + store := h.userdataStore + logdb := h.logDb + hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) if err != nil { logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err) @@ -712,20 +690,26 @@ func (h *MenuHandlers) VerifyCreatePin(ctx context.Context, sym string, input [] } if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) { - res.FlagSet = []uint32{flag_valid_pin} - res.FlagReset = []uint32{flag_pin_mismatch} + res.FlagSet = append(res.FlagSet, flag_valid_pin) res.FlagSet = append(res.FlagSet, flag_pin_set) + res.FlagReset = append(res.FlagReset, flag_pin_mismatch) } else { - res.FlagSet = []uint32{flag_pin_mismatch} + res.FlagSet = append(res.FlagSet, flag_pin_mismatch) return res, nil } + // save the hashed PIN as the new account PIN err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) if err != nil { logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) return res, err } + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write DATA_ACCOUNT_PIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err) + } + return res, nil } @@ -738,7 +722,10 @@ func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []by return res, fmt.Errorf("missing session") } firstName := string(input) + store := h.userdataStore + logdb := h.logDb + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set") @@ -756,6 +743,11 @@ func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []by return res, err } res.FlagSet = append(res.FlagSet, flag_firstname_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName) + } } else { if firstNameSet { err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)) @@ -781,6 +773,7 @@ func (h *MenuHandlers) SaveFamilyname(ctx context.Context, sym string, input []b } store := h.userdataStore + logdb := h.logDb familyName := string(input) flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") @@ -800,6 +793,11 @@ func (h *MenuHandlers) SaveFamilyname(ctx context.Context, sym string, input []b return res, err } res.FlagSet = append(res.FlagSet, flag_familyname_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName) + } } else { if familyNameSet { err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)) @@ -856,6 +854,8 @@ func (h *MenuHandlers) SaveYob(ctx context.Context, sym string, input []byte) (r } yob := string(input) store := h.userdataStore + logdb := h.logDb + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set") @@ -874,6 +874,11 @@ func (h *MenuHandlers) SaveYob(ctx context.Context, sym string, input []byte) (r return res, err } res.FlagSet = append(res.FlagSet, flag_yob_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write yob db log entry", "key", storedb.DATA_YOB, "value", temporaryYob) + } } else { if yobSet { err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)) @@ -899,6 +904,7 @@ func (h *MenuHandlers) SaveLocation(ctx context.Context, sym string, input []byt } location := string(input) store := h.userdataStore + logdb := h.logDb flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_location_set, _ := h.flagManager.GetFlag("flag_location_set") @@ -917,6 +923,11 @@ func (h *MenuHandlers) SaveLocation(ctx context.Context, sym string, input []byt return res, err } res.FlagSet = append(res.FlagSet, flag_location_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write location db log entry", "key", storedb.DATA_LOCATION, "value", temporaryLocation) + } } else { if locationSet { err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)) @@ -944,6 +955,7 @@ func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte) } gender := strings.Split(symbol, "_")[1] store := h.userdataStore + logdb := h.logDb flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set") @@ -962,6 +974,12 @@ func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte) return res, err } res.FlagSet = append(res.FlagSet, flag_gender_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write gender db log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryGender) + } + } else { if genderSet { err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(gender)) @@ -988,6 +1006,7 @@ func (h *MenuHandlers) SaveOfferings(ctx context.Context, sym string, input []by offerings := string(input) store := h.userdataStore + logdb := h.logDb flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set") @@ -1007,6 +1026,11 @@ func (h *MenuHandlers) SaveOfferings(ctx context.Context, sym string, input []by return res, err } res.FlagSet = append(res.FlagSet, flag_offerings_set) + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryOfferings)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write offerings db log entry", "key", storedb.DATA_OFFERINGS, "value", offerings) + } } else { if offeringsSet { err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)) @@ -1306,17 +1330,30 @@ func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, i return res, nil } -// CheckIdentifier retrieves the PublicKey from the JSON data file. +// CheckIdentifier retrieves the Public key from the userdatastore under the key: DATA_PUBLIC_KEY and triggers an sms that +// will be sent to the associated session id func (h *MenuHandlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result + + smsservice := h.smsService + sessionId, ok := ctx.Value("SessionId").(string) if !ok { return res, fmt.Errorf("missing session") } store := h.userdataStore - publicKey, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - + publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } res.Content = string(publicKey) + //trigger an address sms to be delivered to the associated session id + err = smsservice.SendAddressSMS(ctx) + if err != nil { + logg.DebugCtxf(ctx, "Failed to trigger an address sms", "error", err) + return res, nil + } return res, nil } @@ -1717,6 +1754,7 @@ func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input [ func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore + smsservice := h.smsService sessionId, ok := ctx.Value("SessionId").(string) if !ok { @@ -1727,18 +1765,23 @@ func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, inp l := gotext.NewLocale(translationDir, code) l.AddDomain("default") - recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) - if len(recipient) == 0 { - logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE) - return res, fmt.Errorf("Data error encountered") + recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + if err != nil { + logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err) + return res, err } - // TODO - // send an invitation SMS - // if successful - // res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient)) + if !phone.IsValidPhoneNumber(string(recipient)) { + logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient) + return res, nil + } - res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient)) + _, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient)) + if err != nil { + res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient)) + return res, nil + } + res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient)) return res, nil } @@ -1971,6 +2014,7 @@ func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, inpu func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result userStore := h.userdataStore + logdb := h.logDb sessionId, ok := ctx.Value("SessionId").(string) if !ok { @@ -2027,6 +2071,10 @@ func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []b logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err) return res, err } + err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value) + } } logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr) @@ -2224,6 +2272,7 @@ func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input flag_api_error, _ := h.flagManager.GetFlag("flag_api_error") userStore := h.userdataStore + logdb := h.logDb publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) if err != nil { logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) @@ -2264,6 +2313,10 @@ func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err) return res, err } + err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write tx db log entry", "key", key, "value", value) + } } res.FlagReset = append(res.FlagReset, flag_no_transfers) @@ -2551,6 +2604,7 @@ func (h *MenuHandlers) GetSuggestedAlias(ctx context.Context, sym string, input func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore + logdb := h.logDb flag_alias_set, _ := h.flagManager.GetFlag("flag_alias_set") @@ -2569,6 +2623,11 @@ func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input [] return res, err } + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(newAlias)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write account alias db log entry", "key", storedb.DATA_ACCOUNT_ALIAS, "value", newAlias) + } + res.FlagSet = append(res.FlagSet, flag_alias_set) return res, nil } diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index e3fd124..e8f7974 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -62,6 +62,25 @@ func InitializeTestStore(t *testing.T) (context.Context, *store.UserDataStore) { return ctx, store } +// InitializeTestLogdbStore sets up and returns an in-memory database and logdb store. +func InitializeTestLogdbStore(t *testing.T) (context.Context, *store.UserDataStore) { + ctx := context.Background() + + // Initialize memDb + db := memdb.NewMemDb() + err := db.Connect(ctx, "") + require.NoError(t, err, "Failed to connect to memDb") + + // Create UserDataStore with memDb + logdb := &store.UserDataStore{Db: db} + + t.Cleanup(func() { + db.Close(ctx) // Ensure the DB is closed after each test + }) + + return ctx, logdb +} + func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storedb.SubPrefixDb { db := memdb.NewMemDb() err := db.Connect(ctx, "") @@ -76,6 +95,7 @@ func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storedb.SubPr func TestNewMenuHandlers(t *testing.T) { _, store := InitializeTestStore(t) + _, logdb := InitializeTestLogdbStore(t) fm, err := NewFlagManager(flagsPath) if err != nil { @@ -86,7 +106,7 @@ func TestNewMenuHandlers(t *testing.T) { // Test case for valid UserDataStore t.Run("Valid UserDataStore", func(t *testing.T) { - handlers, err := NewMenuHandlers(fm, store, &accountService, mockReplaceSeparator) + handlers, err := NewMenuHandlers(fm, store, logdb, &accountService, mockReplaceSeparator) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -110,7 +130,7 @@ func TestNewMenuHandlers(t *testing.T) { // Test case for nil UserDataStore t.Run("Nil UserDataStore", func(t *testing.T) { - handlers, err := NewMenuHandlers(fm, nil, &accountService, mockReplaceSeparator) + handlers, err := NewMenuHandlers(fm, nil, logdb, &accountService, mockReplaceSeparator) if err == nil { t.Fatal("expected an error, got none") } @@ -192,8 +212,13 @@ func TestInit(t *testing.T) { func TestCreateAccount(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, err := NewFlagManager(flagsPath) if err != nil { @@ -229,8 +254,9 @@ func TestCreateAccount(t *testing.T) { mockAccountService := new(mocks.MockAccountService) h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, accountService: mockAccountService, + logDb: logDb, flagManager: fm, } @@ -268,8 +294,13 @@ func TestWithPersister_PanicWhenAlreadySet(t *testing.T) { func TestSaveFirstname(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, _ := NewFlagManager(flagsPath) @@ -285,7 +316,7 @@ func TestSaveFirstname(t *testing.T) { // Define test data firstName := "John" - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil { t.Fatal(err) } @@ -293,9 +324,10 @@ func TestSaveFirstname(t *testing.T) { // Create the MenuHandlers instance with the mock store h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, flagManager: fm, st: mockState, + logDb: logDb, } // Call the method @@ -306,14 +338,19 @@ func TestSaveFirstname(t *testing.T) { assert.Equal(t, expectedResult, res) // Verify that the DATA_FIRST_NAME entry has been updated with the temporary value - storedFirstName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME) + storedFirstName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME) assert.Equal(t, firstName, string(storedFirstName)) } func TestSaveFamilyname(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, _ := NewFlagManager(flagsPath) @@ -331,15 +368,16 @@ func TestSaveFamilyname(t *testing.T) { // Define test data familyName := "Doeee" - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil { t.Fatal(err) } // Create the MenuHandlers instance with the mock store h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, st: mockState, flagManager: fm, + logDb: logDb, } // Call the method @@ -350,14 +388,19 @@ func TestSaveFamilyname(t *testing.T) { assert.Equal(t, expectedResult, res) // Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value - storedFamilyName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME) + storedFamilyName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME) assert.Equal(t, familyName, string(storedFamilyName)) } func TestSaveYoB(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, _ := NewFlagManager(flagsPath) @@ -373,7 +416,7 @@ func TestSaveYoB(t *testing.T) { // Define test data yob := "1980" - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil { t.Fatal(err) } @@ -381,9 +424,10 @@ func TestSaveYoB(t *testing.T) { // Create the MenuHandlers instance with the mock store h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, flagManager: fm, st: mockState, + logDb: logDb, } // Call the method @@ -394,14 +438,19 @@ func TestSaveYoB(t *testing.T) { assert.Equal(t, expectedResult, res) // Verify that the DATA_YOB entry has been updated with the temporary value - storedYob, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_YOB) + storedYob, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_YOB) assert.Equal(t, yob, string(storedYob)) } func TestSaveLocation(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, _ := NewFlagManager(flagsPath) @@ -417,7 +466,7 @@ func TestSaveLocation(t *testing.T) { // Define test data location := "Kilifi" - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil { t.Fatal(err) } @@ -425,9 +474,10 @@ func TestSaveLocation(t *testing.T) { // Create the MenuHandlers instance with the mock store h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, flagManager: fm, st: mockState, + logDb: logDb, } // Call the method @@ -438,14 +488,19 @@ func TestSaveLocation(t *testing.T) { assert.Equal(t, expectedResult, res) // Verify that the DATA_LOCATION entry has been updated with the temporary value - storedLocation, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION) + storedLocation, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION) assert.Equal(t, location, string(storedLocation)) } func TestSaveOfferings(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, _ := NewFlagManager(flagsPath) @@ -461,7 +516,7 @@ func TestSaveOfferings(t *testing.T) { // Define test data offerings := "Bananas" - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil { t.Fatal(err) } @@ -469,9 +524,10 @@ func TestSaveOfferings(t *testing.T) { // Create the MenuHandlers instance with the mock store h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, flagManager: fm, st: mockState, + logDb: logDb, } // Call the method @@ -482,14 +538,19 @@ func TestSaveOfferings(t *testing.T) { assert.Equal(t, expectedResult, res) // Verify that the DATA_OFFERINGS entry has been updated with the temporary value - storedOfferings, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS) + storedOfferings, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS) assert.Equal(t, offerings, string(storedOfferings)) } func TestSaveGender(t *testing.T) { sessionId := "session123" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, _ := NewFlagManager(flagsPath) @@ -529,16 +590,17 @@ func TestSaveGender(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil { + if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil { t.Fatal(err) } mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol) // Create the MenuHandlers instance with the mock store h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, st: mockState, flagManager: fm, + logDb: logDb, } expectedResult := resource.Result{} @@ -553,7 +615,7 @@ func TestSaveGender(t *testing.T) { assert.Equal(t, expectedResult, res) // Verify that the DATA_GENDER entry has been updated with the temporary value - storedGender, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER) + storedGender, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_GENDER) assert.Equal(t, tt.expectedGender, string(storedGender)) }) } @@ -569,7 +631,7 @@ func TestSaveTemporaryPin(t *testing.T) { log.Fatal(err) } - flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin") + flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin") // Create the MenuHandlers instance with the mock flag manager h := &MenuHandlers{ @@ -587,14 +649,14 @@ func TestSaveTemporaryPin(t *testing.T) { name: "Valid Pin entry", input: []byte("1234"), expectedResult: resource.Result{ - FlagReset: []uint32{flag_incorrect_pin}, + FlagReset: []uint32{flag_invalid_pin}, }, }, { name: "Invalid Pin entry", input: []byte("12343"), expectedResult: resource.Result{ - FlagSet: []uint32{flag_incorrect_pin}, + FlagSet: []uint32{flag_invalid_pin}, }, }, } @@ -1846,53 +1908,7 @@ func TestGetProfile(t *testing.T) { } } -func TestVerifyNewPin(t *testing.T) { - sessionId := "session123" - - fm, _ := NewFlagManager(flagsPath) - mockState := state.NewState(16) - - flag_valid_pin, _ := fm.GetFlag("flag_valid_pin") - mockAccountService := new(mocks.MockAccountService) - h := &MenuHandlers{ - flagManager: fm, - accountService: mockAccountService, - st: mockState, - } - ctx := context.WithValue(context.Background(), "SessionId", sessionId) - - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Test with valid pin", - input: []byte("1234"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_valid_pin}, - }, - }, - { - name: "Test with invalid pin", - input: []byte("123"), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_valid_pin}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - //Call the function under test - res, _ := h.VerifyNewPin(ctx, "verify_new_pin", tt.input) - - //Assert that the result set to content is what was expected - assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input") - }) - } -} - -func TestConfirmPin(t *testing.T) { +func TestConfirmPinChange(t *testing.T) { sessionId := "session123" mockState := state.NewState(16) @@ -1901,6 +1917,8 @@ func TestConfirmPin(t *testing.T) { fm, _ := NewFlagManager(flagsPath) flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch") + flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset") + mockAccountService := new(mocks.MockAccountService) h := &MenuHandlers{ userdataStore: store, @@ -1920,7 +1938,7 @@ func TestConfirmPin(t *testing.T) { input: []byte("1234"), temporarypin: "1234", expectedResult: resource.Result{ - FlagReset: []uint32{flag_pin_mismatch}, + FlagReset: []uint32{flag_pin_mismatch, flag_account_pin_reset}, }, }, } @@ -1997,8 +2015,13 @@ func TestManageVouchers(t *testing.T) { sessionId := "session123" publicKey := "0X13242618721" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } fm, err := NewFlagManager(flagsPath) if err != nil { @@ -2014,7 +2037,7 @@ func TestManageVouchers(t *testing.T) { t.Fatal(err) } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) if err != nil { t.Fatal(err) } @@ -2073,20 +2096,21 @@ func TestManageVouchers(t *testing.T) { mockAccountService := new(mocks.MockAccountService) h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, accountService: mockAccountService, flagManager: fm, + logDb: logDb, } mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) // Store active voucher if needed if tt.storedActiveVoucher != "" { - err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher)) + err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher)) if err != nil { t.Fatal(err) } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds")) + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds")) if err != nil { t.Fatal(err) } @@ -2098,12 +2122,12 @@ func TestManageVouchers(t *testing.T) { if tt.storedActiveVoucher != "" { // Validate stored voucher symbols - voucherData, err := store.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) + voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) assert.NoError(t, err) assert.Equal(t, tt.expectedVoucherSymbols, voucherData) // Validate stored active contract address - updatedAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + updatedAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) assert.NoError(t, err) assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress) @@ -2347,10 +2371,8 @@ func TestCheckBlockedStatus(t *testing.T) { if err != nil { t.Logf(err.Error()) } - flag_account_blocked, err := fm.GetFlag("flag_account_blocked") - if err != nil { - t.Logf(err.Error()) - } + flag_account_blocked, _ := fm.GetFlag("flag_account_blocked") + flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset") h := &MenuHandlers{ userdataStore: store, @@ -2365,13 +2387,15 @@ func TestCheckBlockedStatus(t *testing.T) { { name: "Currently blocked account", currentWrongPinAttempts: "4", - expectedResult: resource.Result{}, + expectedResult: resource.Result{ + FlagReset: []uint32{flag_account_pin_reset}, + }, }, { name: "Account with 0 wrong PIN attempts", currentWrongPinAttempts: "0", expectedResult: resource.Result{ - FlagReset: []uint32{flag_account_blocked}, + FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked}, }, }, } @@ -2432,8 +2456,14 @@ func TestCheckTransactions(t *testing.T) { sessionId := "session123" publicKey := "0X13242618721" - ctx, store := InitializeTestStore(t) + ctx, userStore := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) + _, logdb := InitializeTestLogdbStore(t) + + logDb := store.LogDb{ + Db: logdb, + } + spdb := InitializeTestSubPrefixDb(t, ctx) fm, err := NewFlagManager(flagsPath) @@ -2442,13 +2472,14 @@ func TestCheckTransactions(t *testing.T) { } h := &MenuHandlers{ - userdataStore: store, + userdataStore: userStore, accountService: mockAccountService, prefixDb: spdb, + logDb: logDb, flagManager: fm, } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) if err != nil { t.Fatal(err) } @@ -2900,173 +2931,6 @@ func TestValidateBlockedNumber(t *testing.T) { } } -func TestSaveOthersTemporaryPin(t *testing.T) { - sessionId := "session123" - blockedNumber := "+254712345678" - testPin := "1234" - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - h := &MenuHandlers{ - userdataStore: userStore, - } - - tests := []struct { - name string - sessionId string - blockedNumber string - testPin string - setup func() error // Setup function for each test case - expectedError bool - verifyResult func(t *testing.T) // Function to verify the result - }{ - { - name: "Missing Session ID", - sessionId: "", // Empty session ID - blockedNumber: blockedNumber, - testPin: testPin, - setup: nil, - expectedError: true, - verifyResult: nil, - }, - { - name: "Failed to Read Blocked Number", - sessionId: sessionId, - blockedNumber: blockedNumber, - testPin: testPin, - setup: func() error { - // Do not write the blocked number to simulate a read failure - return nil - }, - expectedError: true, - verifyResult: nil, - }, - - { - name: "Successfully save hashed PIN", - sessionId: sessionId, - blockedNumber: blockedNumber, - testPin: testPin, - setup: func() error { - // Write the blocked number to the store - return userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber)) - }, - expectedError: false, - verifyResult: func(t *testing.T) { - // Read the stored hashed PIN - othersHashedPin, err := userStore.ReadEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE) - if err != nil { - t.Fatal(err) - } - - // Verify that the stored hashed PIN matches the original PIN - if !pin.VerifyPIN(string(othersHashedPin), testPin) { - t.Fatal("stored hashed PIN does not match the original PIN") - } - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Set up the context with the session ID - ctx := context.WithValue(context.Background(), "SessionId", tt.sessionId) - - // Run the setup function if provided - if tt.setup != nil { - err := tt.setup() - if err != nil { - t.Fatal(err) - } - } - - // Call the function under test - _, err := h.SaveOthersTemporaryPin(ctx, "save_others_temporary_pin", []byte(tt.testPin)) - - // Assert the error - if tt.expectedError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - - // Verify the result if a verification function is provided - if tt.verifyResult != nil { - tt.verifyResult(t) - } - }) - } -} - -func TestCheckBlockedNumPinMisMatch(t *testing.T) { - sessionId := "session123" - blockedNumber := "+254712345678" - testPin := "1234" - mockState := state.NewState(128) - - ctx, userStore := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - hashedPIN, err := pin.HashPIN(testPin) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash testPin", "error", err) - t.Fatal(err) - } - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch") - - h := &MenuHandlers{ - userdataStore: userStore, - st: mockState, - flagManager: fm, - } - - // Write initial data to the store - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber)) - if err != nil { - t.Fatal(err) - } - err = userStore.WriteEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - t.Fatal(err) - } - - tests := []struct { - name string - input []byte - expectedResult resource.Result - }{ - { - name: "Successful PIN match", - input: []byte(testPin), - expectedResult: resource.Result{ - FlagReset: []uint32{flag_pin_mismatch}, - }, - }, - { - name: "PIN mismatch", - input: []byte("1345"), - expectedResult: resource.Result{ - FlagSet: []uint32{flag_pin_mismatch}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - res, err := h.CheckBlockedNumPinMisMatch(ctx, "sym", tt.input) - - assert.NoError(t, err) - assert.Equal(t, tt.expectedResult, res) - }) - } -} - func TestGetCurrentProfileInfo(t *testing.T) { sessionId := "session123" ctx, store := InitializeTestStore(t) @@ -3223,30 +3087,6 @@ func TestResetOthersPin(t *testing.T) { assert.NoError(t, err) } -func TestResetValidPin(t *testing.T) { - ctx := context.Background() - - fm, err := NewFlagManager(flagsPath) - if err != nil { - t.Fatal(err) - } - flag_valid_pin, _ := fm.GetFlag("flag_valid_pin") - - expectedResult := resource.Result{ - FlagReset: []uint32{flag_valid_pin}, - } - - h := &MenuHandlers{ - flagManager: fm, - } - - res, err := h.ResetValidPin(ctx, "reset_valid_pin", []byte("")) - - assert.NoError(t, err) - - assert.Equal(t, expectedResult, res) -} - func TestResetUnregisteredNumber(t *testing.T) { ctx := context.Background() diff --git a/handlers/local.go b/handlers/local.go index 2d65d21..69f5883 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -27,6 +27,7 @@ type LocalHandlerService struct { DbRs *resource.DbResource Pe *persist.Persister UserdataStore *db.Db + LogDb *db.Db Cfg engine.Config Rs resource.Resource first resource.EntryFunc @@ -57,12 +58,16 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) { ls.UserdataStore = db } +func (ls *LocalHandlerService) SetLogDb(db *db.Db) { + ls.LogDb = db +} + func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) (*application.MenuHandlers, error) { replaceSeparatorFunc := func(input string) string { return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator) } - appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, accountService, replaceSeparatorFunc) + appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, *ls.LogDb, accountService, replaceSeparatorFunc) if err != nil { return nil, err } @@ -99,7 +104,6 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) 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) @@ -108,13 +112,10 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) 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) diff --git a/internal/sms/sms.go b/internal/sms/sms.go new file mode 100644 index 0000000..f6d51ce --- /dev/null +++ b/internal/sms/sms.go @@ -0,0 +1,96 @@ +package sms + +import ( + "context" + "fmt" + + "git.defalsify.org/vise.git/logging" + "git.grassecon.net/grassrootseconomics/common/phone" + "git.grassecon.net/grassrootseconomics/sarafu-api/remote" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +var ( + logg = logging.NewVanilla().WithDomain("smsservice") +) + +type SmsService struct { + Accountservice remote.AccountService + Userdatastore store.UserDataStore +} + +// SendUpsellSMS will send an invitation SMS to an unregistered phone number +func (smsservice *SmsService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) error { + if !phone.IsValidPhoneNumber(inviterPhone) { + return fmt.Errorf("invalid inviter phone number %v", inviterPhone) + } + + if !phone.IsValidPhoneNumber(inviteePhone) { + return fmt.Errorf("Invalid invitee phone number %v", inviteePhone) + } + _, err := smsservice.Accountservice.SendUpsellSMS(ctx, inviterPhone, inviteePhone) + if err != nil { + return fmt.Errorf("Failed to send upsell sms: %v", err) + } + return nil +} + +// sendPINResetSMS will send an SMS to a user's phonenumber in the event that the associated account's PIN has been reset. +func (smsService *SmsService) SendPINResetSMS(ctx context.Context, adminPhoneNumber, blockedPhoneNumber string) error { + formattedAdminPhone, err := phone.FormatPhoneNumber(adminPhoneNumber) + if err != nil { + return fmt.Errorf("failed to format admin phone number: %w", err) + } + + formattedBlockedPhone, err := phone.FormatPhoneNumber(blockedPhoneNumber) + if err != nil { + return fmt.Errorf("failed to format blocked phone number: %w", err) + } + + if !phone.IsValidPhoneNumber(formattedAdminPhone) { + return fmt.Errorf("invalid admin phone number") + } + if !phone.IsValidPhoneNumber(formattedBlockedPhone) { + return fmt.Errorf("invalid blocked phone number") + } + + err = smsService.Accountservice.SendPINResetSMS(ctx, formattedAdminPhone, formattedBlockedPhone) + if err != nil { + return fmt.Errorf("failed to send pin reset sms: %v", err) + } + + return nil +} + +// SendAddressSMS will triger an SMS when a user navigates to the my address node.The SMS will be sent to the associated phonenumber. +func (smsService *SmsService) SendAddressSMS(ctx context.Context) error { + store := smsService.Userdatastore + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return fmt.Errorf("missing session") + } + + publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return err + } + + originPhone, err := phone.FormatPhoneNumber(sessionId) + if err != nil { + logg.DebugCtxf(ctx, "Failed to format origin phonenumber", "sessionid", sessionId) + return nil + } + + if !phone.IsValidPhoneNumber(originPhone) { + logg.InfoCtxf(ctx, "Invalid origin phone number", "origin phonenumber", originPhone) + return fmt.Errorf("invalid origin phone number") + } + err = smsService.Accountservice.SendAddressSMS(ctx, string(publicKey), originPhone) + if err != nil { + logg.DebugCtxf(ctx, "Failed to send address sms", "error", err) + return fmt.Errorf("Failed to send address sms: %v", err) + } + return nil +} diff --git a/menutraversal_test/group_test.json b/menutraversal_test/group_test.json index 99958dd..38c382e 100644 --- a/menutraversal_test/group_test.json +++ b/menutraversal_test/group_test.json @@ -202,23 +202,7 @@ }, { "input": "0700000000", - "expectedContent": "Please enter new PIN for: {secondary_session_id}\n0:Back" - }, - { - "input": "11111", - "expectedContent": "The PIN you have entered is invalid.Please try a 4 digit number instead.\n1:Retry\n9:Quit" - }, - { - "input": "1", - "expectedContent": "Please enter new PIN for: {secondary_session_id}\n0:Back" - }, - { - "input": "1111", - "expectedContent": "Please confirm new PIN for: {secondary_session_id}\n0:Back" - }, - { - "input": "1111", - "expectedContent": "Please enter your PIN:" + "expectedContent": "{secondary_session_id} will get a PIN reset request.\nPlease enter your PIN to confirm:\n0:Back\n9:Quit" }, { "input": "1234", diff --git a/services/registration/address.vis b/services/registration/address.vis index dfc46d1..a078306 100644 --- a/services/registration/address.vis +++ b/services/registration/address.vis @@ -1,5 +1,4 @@ LOAD check_identifier 0 -RELOAD check_identifier MAP check_identifier MOUT back 0 MOUT quit 9 diff --git a/services/registration/amount.vis b/services/registration/amount.vis index ac3abd1..c50691f 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -6,6 +6,7 @@ MOUT back 0 HALT LOAD validate_amount 64 RELOAD validate_amount +CATCH api_failure flag_api_call_error 1 CATCH invalid_amount flag_invalid_amount 1 INCMP _ 0 LOAD get_recipient 0 diff --git a/services/registration/authorize_reset_others_pin b/services/registration/authorize_reset_others_pin new file mode 100644 index 0000000..1ed5a77 --- /dev/null +++ b/services/registration/authorize_reset_others_pin @@ -0,0 +1,2 @@ +{{.retrieve_blocked_number}} will get a PIN reset request. +Please enter your PIN to confirm: \ No newline at end of file diff --git a/services/registration/authorize_reset_others_pin.vis b/services/registration/authorize_reset_others_pin.vis new file mode 100644 index 0000000..6f9e5b3 --- /dev/null +++ b/services/registration/authorize_reset_others_pin.vis @@ -0,0 +1,12 @@ +LOAD retrieve_blocked_number 0 +RELOAD retrieve_blocked_number +MAP retrieve_blocked_number +MOUT back 0 +MOUT quit 9 +LOAD authorize_account 6 +HALT +RELOAD authorize_account +CATCH incorrect_pin flag_incorrect_pin 1 +INCMP _ 0 +INCMP quit 9 +INCMP pin_reset_result * diff --git a/services/registration/authorize_reset_others_pin_swa b/services/registration/authorize_reset_others_pin_swa new file mode 100644 index 0000000..4f355ac --- /dev/null +++ b/services/registration/authorize_reset_others_pin_swa @@ -0,0 +1,2 @@ +{{.retrieve_blocked_number}} atapokea ombi la kuweka upya PIN. +Tafadhali weka PIN yako kudhibitisha: \ No newline at end of file diff --git a/services/registration/community_balance.vis b/services/registration/community_balance.vis index fad90cc..a748685 100644 --- a/services/registration/community_balance.vis +++ b/services/registration/community_balance.vis @@ -1,6 +1,6 @@ LOAD reset_incorrect 6 LOAD fetch_community_balance 0 -CATCH api_failure flag_api_call_error 1 +CATCH api_failure flag_api_call_error 1 MAP fetch_community_balance CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 diff --git a/services/registration/confirm_create_pin.vis b/services/registration/confirm_create_pin.vis index 02279dc..2e82d7d 100644 --- a/services/registration/confirm_create_pin.vis +++ b/services/registration/confirm_create_pin.vis @@ -1,4 +1,7 @@ -LOAD save_temporary_pin 6 +MOUT back 0 HALT +INCMP _ 0 LOAD verify_create_pin 8 +RELOAD verify_create_pin +CATCH pin_mismatch flag_pin_mismatch 1 INCMP account_creation * diff --git a/services/registration/confirm_new_alias.vis b/services/registration/confirm_new_alias.vis index ad56fe0..ea79412 100644 --- a/services/registration/confirm_new_alias.vis +++ b/services/registration/confirm_new_alias.vis @@ -8,5 +8,5 @@ HALT INCMP _ 0 RELOAD authorize_account CATCH incorrect_pin flag_incorrect_pin 1 -CATCH invalid_pin flag_invalid_pin 1 +CATCH invalid_pin flag_invalid_pin 1 CATCH update_alias flag_allow_update 1 diff --git a/services/registration/confirm_others_new_pin b/services/registration/confirm_others_new_pin deleted file mode 100644 index 720778b..0000000 --- a/services/registration/confirm_others_new_pin +++ /dev/null @@ -1 +0,0 @@ -Please confirm new PIN for: {{.retrieve_blocked_number}} \ No newline at end of file diff --git a/services/registration/confirm_others_new_pin.vis b/services/registration/confirm_others_new_pin.vis deleted file mode 100644 index 50dfb19..0000000 --- a/services/registration/confirm_others_new_pin.vis +++ /dev/null @@ -1,14 +0,0 @@ -CATCH incorrect_pin flag_incorrect_pin 1 -RELOAD retrieve_blocked_number -MAP retrieve_blocked_number -CATCH invalid_others_pin flag_valid_pin 0 -CATCH pin_reset_result flag_account_authorized 1 -LOAD save_others_temporary_pin 6 -RELOAD save_others_temporary_pin -MOUT back 0 -HALT -INCMP _ 0 -LOAD check_pin_mismatch 6 -RELOAD check_pin_mismatch -CATCH others_pin_mismatch flag_pin_mismatch 1 -INCMP pin_entry * diff --git a/services/registration/confirm_others_new_pin_swa b/services/registration/confirm_others_new_pin_swa deleted file mode 100644 index f0b09c8..0000000 --- a/services/registration/confirm_others_new_pin_swa +++ /dev/null @@ -1 +0,0 @@ -Tafadhali thibitisha PIN mpya ya: {{.retrieve_blocked_number}} \ No newline at end of file diff --git a/services/registration/confirm_pin_change.vis b/services/registration/confirm_pin_change.vis index 09b12f8..419662b 100644 --- a/services/registration/confirm_pin_change.vis +++ b/services/registration/confirm_pin_change.vis @@ -1,7 +1,7 @@ -LOAD confirm_pin_change 0 MOUT back 0 HALT INCMP _ 0 +LOAD confirm_pin_change 0 RELOAD confirm_pin_change -CATCH pin_reset_mismatch flag_pin_mismatch 1 -INCMP * pin_reset_success +CATCH pin_mismatch flag_pin_mismatch 1 +INCMP pin_reset_success * diff --git a/services/registration/create_pin.vis b/services/registration/create_pin.vis index 40989ec..b329fc3 100644 --- a/services/registration/create_pin.vis +++ b/services/registration/create_pin.vis @@ -2,8 +2,8 @@ LOAD create_account 0 CATCH account_creation_failed flag_account_creation_failed 1 MOUT exit 0 HALT +INCMP quit 0 LOAD save_temporary_pin 6 RELOAD save_temporary_pin -CATCH . flag_incorrect_pin 1 -INCMP quit 0 +CATCH invalid_pin flag_invalid_pin 1 INCMP confirm_create_pin * diff --git a/services/registration/create_pin_mismatch.vis b/services/registration/create_pin_mismatch.vis deleted file mode 100644 index 91793b5..0000000 --- a/services/registration/create_pin_mismatch.vis +++ /dev/null @@ -1,5 +0,0 @@ -MOUT retry 1 -MOUT quit 9 -HALT -INCMP confirm_create_pin 1 -INCMP quit 9 diff --git a/services/registration/enter_other_number.vis b/services/registration/enter_other_number.vis index 018aad3..56b0558 100644 --- a/services/registration/enter_other_number.vis +++ b/services/registration/enter_other_number.vis @@ -7,4 +7,4 @@ INCMP _ 0 LOAD validate_blocked_number 6 RELOAD validate_blocked_number CATCH unregistered_number flag_unregistered_number 1 -INCMP enter_others_new_pin * +INCMP authorize_reset_others_pin * diff --git a/services/registration/enter_others_new_pin b/services/registration/enter_others_new_pin deleted file mode 100644 index 52ae664..0000000 --- a/services/registration/enter_others_new_pin +++ /dev/null @@ -1 +0,0 @@ -Please enter new PIN for: {{.retrieve_blocked_number}} \ No newline at end of file diff --git a/services/registration/enter_others_new_pin.vis b/services/registration/enter_others_new_pin.vis deleted file mode 100644 index 3f8a5c6..0000000 --- a/services/registration/enter_others_new_pin.vis +++ /dev/null @@ -1,9 +0,0 @@ -LOAD retrieve_blocked_number 0 -RELOAD retrieve_blocked_number -MAP retrieve_blocked_number -MOUT back 0 -HALT -LOAD verify_new_pin 6 -RELOAD verify_new_pin -INCMP _ 0 -INCMP * confirm_others_new_pin diff --git a/services/registration/enter_others_new_pin_swa b/services/registration/enter_others_new_pin_swa deleted file mode 100644 index 77ec2f3..0000000 --- a/services/registration/enter_others_new_pin_swa +++ /dev/null @@ -1 +0,0 @@ -Tafadhali weka PIN mpya ya: {{.retrieve_blocked_number}} \ No newline at end of file diff --git a/services/registration/invalid_others_pin b/services/registration/invalid_others_pin deleted file mode 100644 index acdf45f..0000000 --- a/services/registration/invalid_others_pin +++ /dev/null @@ -1 +0,0 @@ -The PIN you have entered is invalid.Please try a 4 digit number instead. \ No newline at end of file diff --git a/services/registration/invalid_others_pin.vis b/services/registration/invalid_others_pin.vis deleted file mode 100644 index d218e6d..0000000 --- a/services/registration/invalid_others_pin.vis +++ /dev/null @@ -1,5 +0,0 @@ -MOUT retry 1 -MOUT quit 9 -HALT -INCMP enter_others_new_pin 1 -INCMP quit 9 diff --git a/services/registration/invalid_pin b/services/registration/invalid_pin index 607c151..a5031af 100644 --- a/services/registration/invalid_pin +++ b/services/registration/invalid_pin @@ -1 +1 @@ -The PIN you entered is invalid.The PIN must be a 4 digit number. \ No newline at end of file +The PIN you entered is invalid. The PIN must be a 4 digit number. \ No newline at end of file diff --git a/services/registration/my_account.vis b/services/registration/my_account.vis index c0b624e..fc0a634 100644 --- a/services/registration/my_account.vis +++ b/services/registration/my_account.vis @@ -9,7 +9,7 @@ MOUT my_address 6 MOUT my_account_alias 7 MOUT back 0 HALT -INCMP main 0 +INCMP ^ 0 INCMP edit_profile 1 INCMP change_language 2 INCMP balances 3 diff --git a/services/registration/my_account_alias.vis b/services/registration/my_account_alias.vis index 1a6894f..c44cce1 100644 --- a/services/registration/my_account_alias.vis +++ b/services/registration/my_account_alias.vis @@ -6,4 +6,4 @@ INCMP _ 0 LOAD request_custom_alias 0 RELOAD request_custom_alias CATCH api_failure flag_api_call_error 1 -INCMP confirm_new_alias * +INCMP confirm_new_alias * diff --git a/services/registration/my_balance.vis b/services/registration/my_balance.vis index b6094c0..166d18f 100644 --- a/services/registration/my_balance.vis +++ b/services/registration/my_balance.vis @@ -1,6 +1,6 @@ LOAD reset_incorrect 6 LOAD check_balance 0 -CATCH api_failure flag_api_call_error 1 +CATCH api_failure flag_api_call_error 1 MAP check_balance CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 diff --git a/services/registration/new_pin.vis b/services/registration/new_pin.vis index 56705d7..79e95ea 100644 --- a/services/registration/new_pin.vis +++ b/services/registration/new_pin.vis @@ -2,6 +2,5 @@ MOUT back 0 HALT INCMP _ 0 RELOAD save_temporary_pin -RELOAD verify_new_pin -CATCH invalid_pin flag_valid_pin 0 -INCMP * confirm_pin_change +CATCH invalid_pin flag_invalid_pin 1 +INCMP confirm_pin_change * diff --git a/services/registration/old_pin.vis b/services/registration/old_pin.vis index de66e2c..ccc2928 100644 --- a/services/registration/old_pin.vis +++ b/services/registration/old_pin.vis @@ -4,5 +4,5 @@ HALT INCMP _ 0 RELOAD authorize_account CATCH incorrect_pin flag_incorrect_pin 1 -CATCH invalid_pin flag_invalid_pin 1 +CATCH invalid_pin flag_invalid_pin 1 INCMP new_pin * diff --git a/services/registration/others_pin_mismatch b/services/registration/others_pin_mismatch deleted file mode 100644 index deb9fe5..0000000 --- a/services/registration/others_pin_mismatch +++ /dev/null @@ -1 +0,0 @@ -The PIN you have entered is not a match diff --git a/services/registration/others_pin_mismatch_swa b/services/registration/others_pin_mismatch_swa deleted file mode 100644 index 5787790..0000000 --- a/services/registration/others_pin_mismatch_swa +++ /dev/null @@ -1 +0,0 @@ -PIN uliyoweka hailingani.Jaribu tena. \ No newline at end of file diff --git a/services/registration/pin_management.vis b/services/registration/pin_management.vis index ca54ee4..01bd236 100644 --- a/services/registration/pin_management.vis +++ b/services/registration/pin_management.vis @@ -1,7 +1,6 @@ LOAD set_back 6 LOAD authorize_account 16 LOAD reset_allow_update 4 -LOAD verify_new_pin 2 LOAD save_temporary_pin 1 LOAD reset_incorrect 0 LOAD reset_invalid_pin 6 diff --git a/services/registration/create_pin_mismatch b/services/registration/pin_mismatch similarity index 100% rename from services/registration/create_pin_mismatch rename to services/registration/pin_mismatch diff --git a/services/registration/others_pin_mismatch.vis b/services/registration/pin_mismatch.vis similarity index 100% rename from services/registration/others_pin_mismatch.vis rename to services/registration/pin_mismatch.vis diff --git a/services/registration/create_pin_mismatch_swa b/services/registration/pin_mismatch_swa similarity index 100% rename from services/registration/create_pin_mismatch_swa rename to services/registration/pin_mismatch_swa diff --git a/services/registration/pin_reset_mismatch b/services/registration/pin_reset_mismatch deleted file mode 100644 index dc0236b..0000000 --- a/services/registration/pin_reset_mismatch +++ /dev/null @@ -1 +0,0 @@ -The PIN is not a match. Try again diff --git a/services/registration/pin_reset_mismatch.vis b/services/registration/pin_reset_mismatch.vis deleted file mode 100644 index b2421aa..0000000 --- a/services/registration/pin_reset_mismatch.vis +++ /dev/null @@ -1,6 +0,0 @@ -MOUT retry 1 -MOUT quit 9 -HALT -INCMP _ 1 -INCMP quit 9 -INCMP . * diff --git a/services/registration/pin_reset_mismatch_swa b/services/registration/pin_reset_mismatch_swa deleted file mode 100644 index 5787790..0000000 --- a/services/registration/pin_reset_mismatch_swa +++ /dev/null @@ -1 +0,0 @@ -PIN uliyoweka hailingani.Jaribu tena. \ No newline at end of file diff --git a/services/registration/pin_reset_result.vis b/services/registration/pin_reset_result.vis index de877e5..3412473 100644 --- a/services/registration/pin_reset_result.vis +++ b/services/registration/pin_reset_result.vis @@ -1,3 +1,4 @@ +CATCH _ flag_account_authorized 0 LOAD retrieve_blocked_number 0 MAP retrieve_blocked_number LOAD reset_others_pin 6 @@ -5,4 +6,4 @@ MOUT back 0 MOUT quit 9 HALT INCMP ^ 0 -INCMP quit 9 +INCMP quit 9 diff --git a/services/registration/pin_reset_success.vis b/services/registration/pin_reset_success.vis index a3a143f..5535616 100644 --- a/services/registration/pin_reset_success.vis +++ b/services/registration/pin_reset_success.vis @@ -1,6 +1,6 @@ MOUT back 0 MOUT quit 9 HALT -INCMP main 0 +INCMP ^ 0 INCMP quit 9 INCMP . * diff --git a/services/registration/pp.csv b/services/registration/pp.csv index e34de5d..ff26614 100644 --- a/services/registration/pp.csv +++ b/services/registration/pp.csv @@ -9,7 +9,7 @@ flag,flag_account_authorized,15,this is set to allow a user access guarded nodes flag,flag_invalid_recipient,16,this is set when the transaction recipient is invalid flag,flag_invalid_recipient_with_invite,17,this is set when the transaction recipient is valid but not on the platform flag,flag_invalid_amount,18,this is set when the given transaction amount is invalid -flag,flag_incorrect_pin,19,this is set when the provided PIN is invalid or does not match the current account's PIN +flag,flag_incorrect_pin,19,this is set when the provided PIN does not match the current account's PIN flag,flag_valid_pin,20,this is set when the given PIN is valid flag,flag_allow_update,21,this is set to allow a user to update their profile data flag,flag_single_edit,22,this is set to allow a user to edit a single profile item such as year of birth @@ -31,3 +31,4 @@ 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 flag,flag_invalid_pin,39,this is set when the given PIN is invalid(is less than or more than 4 digits) flag,flag_alias_set,40,this is set when an account alias has been assigned to a user +flag,flag_account_pin_reset,41,this is set on an account when an admin triggers a PIN reset for them diff --git a/services/registration/root.vis b/services/registration/root.vis index c57f31c..035db83 100644 --- a/services/registration/root.vis +++ b/services/registration/root.vis @@ -2,13 +2,14 @@ LOAD check_blocked_status 1 RELOAD check_blocked_status LOAD check_account_created 2 RELOAD check_account_created +CATCH self_reset_pin flag_account_pin_reset 1 CATCH blocked_account flag_account_blocked 1 CATCH select_language flag_language_set 0 CATCH terms flag_account_created 0 +CATCH create_pin flag_pin_set 0 LOAD check_account_status 0 RELOAD check_account_status -CATCH api_failure flag_api_call_error 1 +CATCH api_failure flag_api_call_error 1 CATCH account_pending flag_account_pending 1 -CATCH create_pin flag_pin_set 0 CATCH main flag_account_success 1 HALT diff --git a/services/registration/self_reset_pin b/services/registration/self_reset_pin new file mode 100644 index 0000000..ce40705 --- /dev/null +++ b/services/registration/self_reset_pin @@ -0,0 +1,2 @@ +A PIN reset has been done on your account. +Please enter a new four number PIN: \ No newline at end of file diff --git a/services/registration/self_reset_pin.vis b/services/registration/self_reset_pin.vis new file mode 100644 index 0000000..401a4e0 --- /dev/null +++ b/services/registration/self_reset_pin.vis @@ -0,0 +1,6 @@ +LOAD reset_invalid_pin 6 +HALT +LOAD save_temporary_pin 1 +RELOAD save_temporary_pin +CATCH invalid_pin flag_invalid_pin 1 +INCMP confirm_pin_change * diff --git a/services/registration/self_reset_pin_swa b/services/registration/self_reset_pin_swa new file mode 100644 index 0000000..0ef19d0 --- /dev/null +++ b/services/registration/self_reset_pin_swa @@ -0,0 +1,2 @@ +Uwekaji upya wa PIN umefanyika kwenye akaunti yako. +Tafadhali weka PIN mpya ya nambari nne: \ No newline at end of file diff --git a/store/db/db.go b/store/db/db.go index 220ac56..517d0a0 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -65,6 +65,8 @@ const ( DATA_ACCOUNT_ALIAS //currently suggested alias by the api awaiting user's confirmation as accepted account alias DATA_SUGGESTED_ALIAS + //Key used to store a value of 1 for a user to reset their own PIN once they access the menu. + DATA_SELF_PIN_RESET ) const ( diff --git a/store/log_db.go b/store/log_db.go new file mode 100644 index 0000000..d056a2e --- /dev/null +++ b/store/log_db.go @@ -0,0 +1,27 @@ +package store + +import ( + "context" + + visedb "git.defalsify.org/vise.git/db" + "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +type LogDb struct { + visedb.Db +} + +func (db *LogDb) WriteLogEntry(ctx context.Context, sessionId string, typ db.DataTyp, v []byte) error { + db.SetPrefix(visedb.DATATYPE_USERDATA) + db.SetSession(sessionId) + k := storedb.ToBytes(typ) + return db.Put(ctx, k, v) +} + +func (db *LogDb) ReadLogEntry(ctx context.Context, sessionId string, typ db.DataTyp) ([]byte, error) { + db.SetPrefix(visedb.DATATYPE_USERDATA) + db.SetSession(sessionId) + k := storedb.ToBytes(typ) + return db.Get(ctx, k) +} diff --git a/testutil/engine.go b/testutil/engine.go index 0cae8d4..531481d 100644 --- a/testutil/engine.go +++ b/testutil/engine.go @@ -113,6 +113,12 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool, *persist.Pe os.Exit(1) } + logdb, err := menuStorageService.GetLogDb(ctx, userDataStore, "test-db-logs", "user-data") + if err != nil { + fmt.Fprintf(os.Stderr, "get log db error: %v\n", err) + os.Exit(1) + } + dbResource, ok := rs.(*resource.DbResource) if !ok { fmt.Fprintf(os.Stderr, "dbresource cast error") @@ -121,6 +127,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool, *persist.Pe lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs) lhs.SetDataStore(&userDataStore) + lhs.SetLogDb(&logdb) lhs.SetPersister(pe) if err != nil { fmt.Fprintf(os.Stderr, err.Error()) @@ -154,6 +161,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool, *persist.Pe en := lhs.GetEngine(lhs.Cfg, rs, pe) cleanFn := func() { + logdb.Close(ctx) err := en.Finish(ctx) if err != nil { logg.Errorf(err.Error())