From a9e0aebddef188c3a7a55241de5498c32a03e257 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Tue, 1 Jul 2025 01:50:20 +0300 Subject: [PATCH] move PIN functionality to pin.go --- handlers/application/menuhandler.go | 359 --------------------------- handlers/application/pin.go | 372 ++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+), 359 deletions(-) create mode 100644 handlers/application/pin.go diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 0107278..13f3ee1 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -266,99 +266,6 @@ func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input return res, nil } -// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. -func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - store := h.userdataStore - - flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") - flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - res.FlagReset = append(res.FlagReset, flag_incorrect_pin) - - currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - if err != nil { - if !db.IsNotFound(err) { - return res, err - } - } - pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - remainingPINAttempts := pin.AllowedPINAttempts - uint8(pinAttemptsValue) - if remainingPINAttempts == 0 { - res.FlagSet = append(res.FlagSet, flag_account_blocked) - return res, nil - } - if remainingPINAttempts < pin.AllowedPINAttempts { - res.Content = strconv.Itoa(int(remainingPINAttempts)) - } - - 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. -func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - var err error - - sessionId, ok := ctx.Value("SessionId").(string) - if !ok { - return res, fmt.Errorf("missing session") - } - - flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") - - if string(input) == "0" { - return res, nil - } - - 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)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to hash the PIN", "error", err) - return res, err - } - - 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 - } - - err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) - if err != nil { - logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) - } - - 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 - flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") - res.FlagReset = append(res.FlagReset, flag_invalid_pin) - return res, nil -} - // ResetApiCallFailure resets the api call failure flag func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -367,199 +274,6 @@ func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, inpu return res, nil } -// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN. -func (h *MenuHandlers) ConfirmPinChange(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") - } - 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) - 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) - 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 - } - - // 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, "hashedPIN 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 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. 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") - } - blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) - return res, err - } - - // 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, nil - } - - err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) - if err != nil { - 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 -} - -// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts -func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error { - var pinAttemptsCount uint8 - store := h.userdataStore - - currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) - if err != nil { - if db.IsNotFound(err) { - //First time Wrong PIN attempt: initialize with a count of 1 - pinAttemptsCount = 1 - err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err) - return err - } - return nil - } - } - pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) - pinAttemptsCount = uint8(pinAttemptsValue) + 1 - - err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err) - return err - } - return nil -} - -// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry -func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error { - store := h.userdataStore - err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) - if err != nil { - logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) - return err - } - return nil -} - // ResetUnregisteredNumber clears the unregistered number flag in the system, // indicating that a number's registration status should no longer be marked as unregistered. func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) { @@ -569,63 +283,6 @@ func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, 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 - - 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) - 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.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 = 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 -} - // SaveFirstname updates the first name in the gdbm with the provided input. func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result @@ -1821,22 +1478,6 @@ func (h *MenuHandlers) GetRecipient(ctx context.Context, sym string, input []byt return res, nil } -// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress. -func (h *MenuHandlers) RetrieveBlockedNumber(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") - } - store := h.userdataStore - blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) - - res.Content = string(blockedNumber) - - return res, nil -} - // GetSender returns the sessionId (phoneNumber). func (h *MenuHandlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result diff --git a/handlers/application/pin.go b/handlers/application/pin.go new file mode 100644 index 0000000..d3999f3 --- /dev/null +++ b/handlers/application/pin.go @@ -0,0 +1,372 @@ +package application + +import ( + "context" + "fmt" + "strconv" + + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/resource" + "git.grassecon.net/grassrootseconomics/common/phone" + "git.grassecon.net/grassrootseconomics/common/pin" + storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db" +) + +// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. +func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + store := h.userdataStore + + flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") + flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if !db.IsNotFound(err) { + return res, err + } + } + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + remainingPINAttempts := pin.AllowedPINAttempts - uint8(pinAttemptsValue) + if remainingPINAttempts == 0 { + res.FlagSet = append(res.FlagSet, flag_account_blocked) + return res, nil + } + if remainingPINAttempts < pin.AllowedPINAttempts { + res.Content = strconv.Itoa(int(remainingPINAttempts)) + } + + 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. +func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") + + if string(input) == "0" { + return res, nil + } + + 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)) + if err != nil { + logg.ErrorCtxf(ctx, "failed to hash the PIN", "error", err) + return res, err + } + + 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 + } + + err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN)) + if err != nil { + logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err) + } + + 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 + flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin") + res.FlagReset = append(res.FlagReset, flag_invalid_pin) + return res, nil +} + +// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN. +func (h *MenuHandlers) ConfirmPinChange(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") + } + 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) + 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) + 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 + } + + // 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, "hashedPIN 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 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. 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") + } + blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err) + return res, err + } + + // 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, nil + } + + err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) + if err != nil { + 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 +} + +// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts +func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error { + var pinAttemptsCount uint8 + store := h.userdataStore + + currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS) + if err != nil { + if db.IsNotFound(err) { + //First time Wrong PIN attempt: initialize with a count of 1 + pinAttemptsCount = 1 + err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err) + return err + } + return nil + } + } + pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64) + pinAttemptsCount = uint8(pinAttemptsValue) + 1 + + err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount)))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err) + return err + } + return nil +} + +// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry +func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error { + store := h.userdataStore + err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0"))) + if err != nil { + logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err) + return err + } + return 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 + + 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) + 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.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 = 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 +} + +// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress. +func (h *MenuHandlers) RetrieveBlockedNumber(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") + } + store := h.userdataStore + blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER) + + res.Content = string(blockedNumber) + + return res, nil +}