Compare commits

...

55 Commits

Author SHA1 Message Date
42177aadeb
use latest commit from sarafu-api
Some checks are pending
release / docker (push) Waiting to run
2025-04-11 11:10:48 +03:00
fa9f1a72ef Merge pull request 'self pin reset' (#49) from self-pin-reset-node into master
Reviewed-on: #49
2025-04-11 09:26:11 +02:00
4ffb32f14a Merge branch 'master' into self-pin-reset-node 2025-04-11 10:25:35 +03:00
c41277db87 Merge pull request 'sms-upsell' (#55) from sms-upsell into master
Reviewed-on: #55
2025-04-11 09:24:35 +02:00
56cda36aa7
updated the description of DATA_SELF_PIN_RESET 2025-04-10 13:55:00 +03:00
c9170ca45a Merge branch 'master' into self-pin-reset-node 2025-04-09 23:43:10 +03:00
57a1645c03
remove the verify_new_pin functionality that managed the unused flag_valid_pin 2025-04-09 23:40:03 +03:00
7d9c3b66a9
remove unused function reset_valid_pin 2025-04-09 19:29:40 +03:00
aec1f4c4c1
use a single confirm_pin_change node 2025-04-09 19:23:46 +03:00
d680387ef1
use the same check for flag_invalid_pin to move to the invalid_pin node 2025-04-09 19:21:35 +03:00
0337c66f96
cleanup: remove extra space in all .vis files 2025-04-09 18:17:59 +03:00
6070792fe4
cleanup: remove extra space 2025-04-09 18:14:10 +03:00
69ae494b2c
update the order for the INCMP statement 2025-04-09 18:10:51 +03:00
b89abf3487
update the reset flags in TestCheckBlockedStatus 2025-04-09 18:06:24 +03:00
4ef8c47f8b
update the reset flags in TestConfirmPinChange 2025-04-09 18:01:40 +03:00
1076a9578e
use the correct flag_invalid_pin in TestSaveTemporaryPin 2025-04-09 17:57:54 +03:00
a27d44e561
updated the expected content for the alias in view profile 2025-04-09 17:52:58 +03:00
35a2732fe2
updated the menu flow for the admin reset others PIN in test 2025-04-09 17:52:18 +03:00
834f2ce629
remove code related to admins resetting a user's actual PIN 2025-04-09 17:39:41 +03:00
26353bdf6e
add a space 2025-04-09 17:34:24 +03:00
1f6bf2bbed
updated the flags set and reset in SaveTemporaryPin and VerifyCreatePin 2025-04-09 16:59:48 +03:00
0c1d9ab582
updated the description of the flag_incorrect_pin 2025-04-09 16:58:13 +03:00
29d94bb2e5
have the catch for create_pin above to catch accounts that do not have a PIN before the status on chain is checked 2025-04-09 16:57:22 +03:00
067c496244
use a single pin_mismatch node for a confirmed PIN that does not match the original PIN 2025-04-09 16:55:46 +03:00
f8c258a3b4
allow the user to quit and move to invalid_pin node if the given input is invalid 2025-04-09 16:49:04 +03:00
aec96ce9ba
move to the top node when one selects back 2025-04-09 15:19:23 +03:00
71ef950fff
use the confirm_self_pin_reset to confirm the new PIN 2025-04-09 15:12:51 +03:00
477f3a307a
use the self_reset_pin node to reset PINs for accounts with a flag_account_pin_reset 2025-04-09 15:11:51 +03:00
33c376c971
set the DATA_SELF_PIN_RESET as 0 and reset the flag_account_pin_reset 2025-04-09 15:10:12 +03:00
514f6ae05b
move back to the previous node if the admin user is not authorized 2025-04-08 16:26:21 +03:00
8a6659a98b
request the admin to authorize the PIN reset 2025-04-08 16:25:50 +03:00
d094af9c51
move to the authorize_reset_others_pin if the given number is valid and registered 2025-04-08 16:25:11 +03:00
f96f9c11e6
updated the ResetOthersPin and ValidateBlockedNumber for the updated reset other's PIN functionality 2025-04-08 16:23:21 +03:00
1d8b0ef951
Merge branch 'master' into sms-upsell 2025-04-07 11:45:29 +03:00
fb2dc003b8
check recipient before sending an sms 2025-04-07 11:38:52 +03:00
a11ca2a618
reset the flag_language_set and flag_account_created flags if the publicKey does not exist on the db
Some checks failed
release / docker (push) Has been cancelled
2025-04-04 12:05:04 +03:00
4268cc0589
build: logdebug mode
Some checks failed
release / docker (push) Has been cancelled
2025-04-03 15:45:41 +03:00
ae0672c7da
updated the vise version to pop the cache on a new session
Some checks failed
release / docker (push) Has been cancelled
2025-04-02 18:34:04 +03:00
2c671bc0d4
use the DATA_SELF_PIN_RESET to set the flag_account_pin_reset 2025-04-02 18:29:04 +03:00
c5673b339b
added a DATA_SELF_PIN_RESET to track when a user's PIN is reset by an admin 2025-04-02 18:24:34 +03:00
c3567313af Merge pull request 'manage vouchers in single function' (#42) from manage-vouchers-in-single-function into master
Reviewed-on: #42
2025-04-01 21:26:28 +02:00
f82b413e34 Merge branch 'master' into manage-vouchers-in-single-function 2025-04-01 21:26:16 +02:00
695bfed349
feat: send invites when a recipient
is not resolved
2025-04-01 16:30:08 +03:00
8bf48cb081
update sarafu-api dep 2025-04-01 16:25:59 +03:00
ea0fc4491d
removed debug related logs 2025-04-01 11:12:38 +03:00
2634790118 Merge pull request 'use a different key for the suggested aliases' (#47) from fix-double-alias-request into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: #47
2025-03-28 09:35:10 +01:00
272a7ac05f
chore: remove extra colon 2025-03-28 11:32:24 +03:00
39c0d31995
remove verbose logs 2025-03-28 11:26:32 +03:00
18acb53ead
add alias logs
Some checks failed
release / docker (push) Has been cancelled
2025-03-28 11:08:50 +03:00
43d91f84f6
use a different key for the suggested aliases 2025-03-28 10:49:31 +03:00
0914217769
increase the size limit for the manage_vouchers 2025-03-26 14:33:23 +03:00
ea117b7222
replace SetDefaultVoucher and CheckVouchers with ManageVouchers 2025-03-26 14:28:43 +03:00
39e1c84a45
added TestManageVouchers for ManageVouchers 2025-03-26 14:27:17 +03:00
20ee4dfb24
replace SetDefaultVoucher and CheckVouchers with ManageVouchers 2025-03-26 14:26:31 +03:00
0791eb1f11
ci/cd: build with log warn only
Some checks failed
release / docker (push) Has been cancelled
2025-03-25 09:52:37 +03:00
52 changed files with 329 additions and 727 deletions

View File

@ -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 logtrace,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

4
go.mod
View File

@ -3,9 +3,9 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise
go 1.23.4
require (
git.defalsify.org/vise.git v0.3.1
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805
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/sarafu-api v0.9.0-beta.1.0.20250411080608-34957e5b6ff8
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694
github.com/alecthomas/assert/v2 v2.2.2

10
go.sum
View File

@ -1,9 +1,19 @@
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.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805 h1:FnT39aqXcP5YWhwPDBABopSjCu2SlbPFoOVitSpAVxU=
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805/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/sarafu-api v0.9.0-beta.1.0.20250401111804-2eed990921c5 h1:DwBZHP4sebfHxK8EU2nlA2CXU81+a7Kj/pnC5vDPcf4=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401111804-2eed990921c5/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401115503-5b41c8dc6440 h1:eWrBZMM3pBMCFyRl4rO/aaR+OmOMFJxogNyFAFry+EM=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401115503-5b41c8dc6440/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401122510-441e289854ad h1:tYjanaCf6mF+iXRtDx5gckQm5vhZYx9N/JlNIBZj1m0=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401122510-441e289854ad/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250411080608-34957e5b6ff8 h1:Emesd0rybSLhPwZwqdvLsk/P9ZsT+7CQwQV/mrjQp3o=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250411080608-34957e5b6ff8/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.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E=

View File

@ -245,6 +245,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
@ -256,35 +257,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) {
@ -293,7 +305,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
@ -336,29 +347,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.
@ -371,15 +359,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))
@ -398,87 +391,6 @@ func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input [
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)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err)
return res, 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
}
// ResetInvalidPIN resets the invalid PIN flag
func (h *MenuHandlers) ResetInvalidPIN(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
@ -495,6 +407,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)
@ -525,14 +438,68 @@ func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input [
logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "hashedPIN value", hashedTemporaryPin, "error", err)
return res, 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
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
}
// 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
@ -545,19 +512,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")))
@ -628,64 +587,26 @@ 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
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
if err != nil {
@ -698,14 +619,15 @@ 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)
@ -1709,19 +1631,24 @@ 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
}
_, err = h.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
}
// ResetTransactionAmount resets the transaction amount and invalid flag.
@ -1946,9 +1873,11 @@ func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, inpu
return res, nil
}
// SetDefaultVoucher retrieves the current vouchers
// and sets the first as the default voucher, if no active voucher is set.
func (h *MenuHandlers) SetDefaultVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and
// 1. sets the first as the default voucher if no active voucher is set.
// 2. Stores list of vouchers
// 3. updates the balance of the active voucher
func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
userStore := h.userdataStore
@ -1959,30 +1888,31 @@ func (h *MenuHandlers) SetDefaultVoucher(ctx context.Context, sym string, input
flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
// check if the user has an active sym
_, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
if err != nil {
if db.IsNotFound(err) {
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)
logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Fetch vouchers from the API using the public key
// Fetch vouchers from API
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
if err != nil {
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
return res, nil
}
// Return if there is no voucher
if len(vouchersResp) == 0 {
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
return res, nil
}
// Use only the first voucher
res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
// Check if user has an active voucher with proper error handling
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
if err != nil {
if db.IsNotFound(err) {
// No active voucher, set the first one as default
firstVoucher := vouchersResp[0]
defaultSym := firstVoucher.TokenSymbol
defaultBal := firstVoucher.Balance
@ -1992,71 +1922,27 @@ func (h *MenuHandlers) SetDefaultVoucher(ctx context.Context, sym string, input
// Scale down the balance
scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec)
// TODO: implement atomic transaction
// set the active symbol
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(defaultSym))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write defaultSym entry with", "key", storedb.DATA_ACTIVE_SYM, "value", defaultSym, "error", err)
return res, err
}
// set the active balance
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL, []byte(scaledBalance))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write defaultBal entry with", "key", storedb.DATA_ACTIVE_BAL, "value", scaledBalance, "error", err)
return res, err
}
// set the active decimals
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL, []byte(defaultDec))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write defaultDec entry with", "key", storedb.DATA_ACTIVE_DECIMAL, "value", defaultDec, "error", err)
return res, err
}
// set the active contract address
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(defaultAddr))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write defaultAddr entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "value", defaultAddr, "error", err)
return res, err
firstVoucherMap := map[storedb.DataTyp]string{
storedb.DATA_ACTIVE_SYM: defaultSym,
storedb.DATA_ACTIVE_BAL: scaledBalance,
storedb.DATA_ACTIVE_DECIMAL: defaultDec,
storedb.DATA_ACTIVE_ADDRESS: defaultAddr,
}
return res, nil
for key, value := range firstVoucherMap {
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err)
return res, err
}
}
logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr)
} else {
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
return res, err
}
res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
return res, nil
}
// CheckVouchers retrieves the token holdings from the API using the "PublicKey" and stores
// them to gdbm.
func (h *MenuHandlers) CheckVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
userStore := h.userdataStore
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)
return res, err
}
// Fetch vouchers from the API using the public key
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
if err != nil {
return res, nil
}
logg.InfoCtxf(ctx, "fetched user vouchers", "public_key", string(publicKey), "vouchers", vouchersResp)
// check the current active sym and update the data
activeSym, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
if activeSym != nil {
} else {
// Update active voucher balance
activeSymStr := string(activeSym)
// Find the matching voucher data
@ -2086,14 +1972,8 @@ func (h *MenuHandlers) CheckVouchers(ctx context.Context, sym string, input []by
}
}
activeBal, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
activeAddr, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
logg.InfoCtxf(ctx, "The active data in CheckVouchers:", "activeSym", string(activeSym), string(activeBal), string(activeAddr))
data := store.ProcessVouchers(vouchersResp)
// Store all voucher data
data := store.ProcessVouchers(vouchersResp)
dataMap := map[storedb.DataTyp]string{
storedb.DATA_VOUCHER_SYMBOLS: data.Symbols,
storedb.DATA_VOUCHER_BALANCES: data.Balances,
@ -2103,7 +1983,6 @@ func (h *MenuHandlers) CheckVouchers(ctx context.Context, sym string, input []by
// Write data entries
for key, value := range dataMap {
logg.InfoCtxf(ctx, "Writing data entry for sessionId: %s", sessionId, "key", key, "value", value)
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
continue
@ -2528,9 +2407,10 @@ func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input
return res, fmt.Errorf("Failed to retrieve alias: %s", err.Error())
}
alias := aliasResult.Alias
logg.InfoCtxf(ctx, "Suggested alias ", "alias", alias)
//Store the returned alias,wait for user to confirm it as new account alias
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(alias))
err = store.WriteEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS, []byte(alias))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write account alias", "key", storedb.DATA_TEMPORARY_VALUE, "value", alias, "error", err)
return res, err
@ -2559,7 +2439,7 @@ func (h *MenuHandlers) GetSuggestedAlias(ctx context.Context, sym string, input
if !ok {
return res, fmt.Errorf("missing session")
}
suggestedAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
suggestedAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS)
if err != nil {
return res, nil
}
@ -2567,7 +2447,7 @@ func (h *MenuHandlers) GetSuggestedAlias(ctx context.Context, sym string, input
return res, nil
}
// ConfirmNewAlias reads the suggested alias from the temporary value and confirms it as the new account alias.
// ConfirmNewAlias reads the suggested alias from the [DATA_SUGGECTED_ALIAS] key and confirms it as the new account alias.
func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
@ -2578,10 +2458,11 @@ func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []
if !ok {
return res, fmt.Errorf("missing session")
}
newAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
newAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS)
if err != nil {
return res, nil
}
logg.InfoCtxf(ctx, "Confirming new alias", "alias", string(newAlias))
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(string(newAlias)))
if err != nil {
logg.ErrorCtxf(ctx, "failed to clear DATA_ACCOUNT_ALIAS_VALUE entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "value", "empty", "error", err)

View File

@ -566,7 +566,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{
@ -584,14 +584,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},
},
},
}
@ -1843,53 +1843,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)
@ -1898,6 +1852,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,
@ -1917,7 +1873,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},
},
},
}
@ -1990,36 +1946,46 @@ func TestFetchCommunityBalance(t *testing.T) {
}
}
func TestSetDefaultVoucher(t *testing.T) {
func TestManageVouchers(t *testing.T) {
sessionId := "session123"
publicKey := "0X13242618721"
ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
t.Fatal(err)
}
flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher")
if err != nil {
t.Logf(err.Error())
t.Fatal(err)
}
publicKey := "0X13242618721"
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
vouchersResp []dataserviceapi.TokenHoldings
storedActiveVoucher string
expectedVoucherSymbols []byte
expectedUpdatedAddress []byte
expectedResult resource.Result
}{
{
name: "Test no vouchers available",
name: "No vouchers available",
vouchersResp: []dataserviceapi.TokenHoldings{},
expectedVoucherSymbols: []byte(""),
expectedUpdatedAddress: []byte(""),
expectedResult: resource.Result{
FlagSet: []uint32{flag_no_active_voucher},
},
},
{
name: "Test set default voucher when no active voucher is set",
name: "Set default voucher when no active voucher is set",
vouchersResp: []dataserviceapi.TokenHoldings{
{
ContractAddress: "0x123",
@ -2028,7 +1994,24 @@ func TestSetDefaultVoucher(t *testing.T) {
Balance: "100",
},
},
expectedResult: resource.Result{},
expectedVoucherSymbols: []byte("1:TOKEN1"),
expectedUpdatedAddress: []byte("0x123"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_no_active_voucher},
},
},
{
name: "Check and update active voucher balance",
vouchersResp: []dataserviceapi.TokenHoldings{
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
},
storedActiveVoucher: "SRF",
expectedVoucherSymbols: []byte("1:SRF\n2:MILO"),
expectedUpdatedAddress: []byte("0xd4c288865Ce"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_no_active_voucher},
},
},
}
@ -2042,49 +2025,11 @@ func TestSetDefaultVoucher(t *testing.T) {
flagManager: fm,
}
err := store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("some-input"))
assert.NoError(t, err)
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
mockAccountService.AssertExpectations(t)
})
}
}
func TestCheckVouchers(t *testing.T) {
mockAccountService := new(mocks.MockAccountService)
sessionId := "session123"
publicKey := "0X13242618721"
ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId)
h := &MenuHandlers{
userdataStore: store,
accountService: mockAccountService,
}
err := store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
mockVouchersResponse := []dataserviceapi.TokenHoldings{
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
}
// store the default voucher data
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte("SRF"))
// Store active voucher if needed
if tt.storedActiveVoucher != "" {
err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher))
if err != nil {
t.Fatal(err)
}
@ -2092,33 +2037,27 @@ func TestCheckVouchers(t *testing.T) {
if err != nil {
t.Fatal(err)
}
}
expectedSym := []byte("1:SRF\n2:MILO")
expectedUpdatedAddress := []byte("0xd4c288865Ce")
mockAccountService.On("FetchVouchers", string(publicKey)).Return(mockVouchersResponse, nil)
_, err = h.CheckVouchers(ctx, "check_vouchers", []byte(""))
res, err := h.ManageVouchers(ctx, "manage_vouchers", []byte(""))
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, res)
// Read voucher sym data from the store
if tt.storedActiveVoucher != "" {
// Validate stored voucher symbols
voucherData, err := store.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
if err != nil {
t.Fatal(err)
}
assert.NoError(t, err)
assert.Equal(t, tt.expectedVoucherSymbols, voucherData)
// Read active contract address from the store
// Validate stored active contract address
updatedAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
if err != nil {
t.Fatal(err)
}
// assert that the data is stored correctly
assert.Equal(t, expectedSym, voucherData)
// assert that the address is updated
assert.Equal(t, expectedUpdatedAddress, updatedAddress)
assert.NoError(t, err)
assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress)
mockAccountService.AssertExpectations(t)
}
})
}
}
func TestGetVoucherList(t *testing.T) {
@ -2353,10 +2292,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,
@ -2371,13 +2308,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},
},
},
}
@ -2906,173 +2845,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)
@ -3229,30 +3001,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()

View File

@ -99,23 +99,18 @@ 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)
ls.DbRs.AddLocalFunc("set_default_voucher", appHandlers.SetDefaultVoucher)
ls.DbRs.AddLocalFunc("check_vouchers", appHandlers.CheckVouchers)
ls.DbRs.AddLocalFunc("manage_vouchers", appHandlers.ManageVouchers)
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)

View File

@ -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",
@ -612,7 +596,7 @@
},
{
"input": "1234",
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\nYour alias: \n\n0:Back\n9:Quit"
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\nYour alias: Not Provided\n\n0:Back\n9:Quit"
},
{
"input": "0",

View File

@ -0,0 +1,2 @@
{{.retrieve_blocked_number}} will get a PIN reset request.
Please enter your PIN to confirm:

View File

@ -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 *

View File

@ -0,0 +1,2 @@
{{.retrieve_blocked_number}} atapokea ombi la kuweka upya PIN.
Tafadhali weka PIN yako kudhibitisha:

View File

@ -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 *

View File

@ -1 +0,0 @@
Please confirm new PIN for: {{.retrieve_blocked_number}}

View File

@ -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 *

View File

@ -1 +0,0 @@
Tafadhali thibitisha PIN mpya ya: {{.retrieve_blocked_number}}

View File

@ -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 *

View File

@ -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 *

View File

@ -1,5 +0,0 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP confirm_create_pin 1
INCMP quit 9

View File

@ -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 *

View File

@ -1 +0,0 @@
Please enter new PIN for: {{.retrieve_blocked_number}}

View File

@ -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

View File

@ -1 +0,0 @@
Tafadhali weka PIN mpya ya: {{.retrieve_blocked_number}}

View File

@ -1 +0,0 @@
The PIN you have entered is invalid.Please try a 4 digit number instead.

View File

@ -1,5 +0,0 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP enter_others_new_pin 1
INCMP quit 9

View File

@ -1 +1 @@
The PIN you entered is invalid.The PIN must be a 4 digit number.
The PIN you entered is invalid. The PIN must be a 4 digit number.

View File

@ -1,9 +1,7 @@
LOAD clear_temporary_value 2
RELOAD clear_temporary_value
LOAD set_default_voucher 8
RELOAD set_default_voucher
LOAD check_vouchers 10
RELOAD check_vouchers
LOAD manage_vouchers 160
RELOAD manage_vouchers
LOAD check_balance 128
RELOAD check_balance
CATCH api_failure flag_api_call_error 1

View File

@ -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

View File

@ -1,2 +1,2 @@
Current alias: {{.get_current_profile_info}}
Enter your preferred alias::
Enter your preferred alias:

View File

@ -1,2 +1,2 @@
Lakabu ya sasa: {{.get_current_profile_info}}
Weka lakabu unalopendelea::
Weka lakabu unalopendelea:

View File

@ -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 *

View File

@ -1 +0,0 @@
The PIN you have entered is not a match

View File

@ -1 +0,0 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@ -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

View File

@ -1 +0,0 @@
The PIN is not a match. Try again

View File

@ -1,6 +0,0 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 1
INCMP quit 9
INCMP . *

View File

@ -1 +0,0 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@ -1,3 +1,4 @@
CATCH _ flag_account_authorized 0
LOAD retrieve_blocked_number 0
MAP retrieve_blocked_number
LOAD reset_others_pin 6

View File

@ -1,6 +1,6 @@
MOUT back 0
MOUT quit 9
HALT
INCMP main 0
INCMP ^ 0
INCMP quit 9
INCMP . *

View File

@ -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

1 flag flag_language_set 8 checks whether the user has set their prefered language
9 flag flag_invalid_recipient 16 this is set when the transaction recipient is invalid
10 flag flag_invalid_recipient_with_invite 17 this is set when the transaction recipient is valid but not on the platform
11 flag flag_invalid_amount 18 this is set when the given transaction amount is invalid
12 flag flag_incorrect_pin 19 this is set when the provided PIN is invalid or does not match the current account's PIN this is set when the provided PIN does not match the current account's PIN
13 flag flag_valid_pin 20 this is set when the given PIN is valid
14 flag flag_allow_update 21 this is set to allow a user to update their profile data
15 flag flag_single_edit 22 this is set to allow a user to edit a single profile item such as year of birth
31 flag flag_account_blocked 38 this is set when an account has been blocked after the allowed incorrect PIN attempts have been exceeded
32 flag flag_invalid_pin 39 this is set when the given PIN is invalid(is less than or more than 4 digits)
33 flag flag_alias_set 40 this is set when an account alias has been assigned to a user
34 flag flag_account_pin_reset 41 this is set on an account when an admin triggers a PIN reset for them

View File

@ -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 account_pending flag_account_pending 1
CATCH create_pin flag_pin_set 0
CATCH main flag_account_success 1
HALT

View File

@ -0,0 +1,2 @@
A PIN reset has been done on your account.
Please enter a new four number PIN:

View File

@ -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 *

View File

@ -0,0 +1,2 @@
Uwekaji upya wa PIN umefanyika kwenye akaunti yako.
Tafadhali weka PIN mpya ya nambari nne:

View File

@ -63,6 +63,10 @@ const (
DATA_INITIAL_LANGUAGE_CODE
//Fully qualified account alias string
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 (