Compare commits

...

11 Commits

8 changed files with 180 additions and 13 deletions

View File

@ -16,3 +16,7 @@ CREATE_ACCOUNT_URL=http://localhost:5003/api/v2/account/create
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
TRACK_URL=http://localhost:5003/api/v2/account/status
#numbers with privileges to reset others pin
ADMIN_NUMBERS=254051722XXX,255012221XXX

View File

@ -15,6 +15,7 @@ import (
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/utils"
)
var (
@ -48,6 +49,9 @@ func main() {
ctx = context.WithValue(ctx, "Database", database)
pfp := path.Join(scriptDir, "pp.csv")
as := utils.NewAdminStore("admin_numbers.txt")
as.Seed()
cfg := engine.Config{
Root: "root",
SessionId: sessionId,

View File

@ -54,7 +54,7 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
}
func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore,accountService)
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, accountService)
if err != nil {
return nil, err
}
@ -98,6 +98,10 @@ func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceIn
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher)
ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher)
ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin)
ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch)
ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber)
ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber)
return ussdHandlers, nil
}

View File

@ -27,11 +27,12 @@ import (
)
var (
logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
scriptDir = path.Join("services", "registration")
translationDir = path.Join(scriptDir, "locale")
okResponse *api.OKResponse
errResponse *api.ErrResponse
logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
scriptDir = path.Join("services", "registration")
translationDir = path.Join(scriptDir, "locale")
PINChangePrivilege byte = 1
okResponse *api.OKResponse
errResponse *api.ErrResponse
)
// FlagManager handles centralized flag management
@ -100,13 +101,27 @@ func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var r resource.Result
if h.pe == nil {
logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
return r, nil
}
h.st = h.pe.GetState()
h.ca = h.pe.GetMemory()
sessionId, _ := ctx.Value("SessionId").(string)
flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege")
number, _ := strconv.ParseInt(sessionId, 10, 64)
as := utils.NewAdminStore("admin_numbers.txt")
isAdmin, _ := as.IsAdmin(number)
if isAdmin {
r.FlagSet = append(r.FlagSet, flag_admin_privilege)
} else {
r.FlagReset = append(r.FlagReset, flag_admin_privilege)
}
if h.st == nil || h.ca == nil {
logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
return r, fmt.Errorf("cannot get state and memory for handler")
@ -189,6 +204,26 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, nil
}
func (h *Handlers) CheckPinMisMatch(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")
}
store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
if err != nil {
return res, err
}
if bytes.Equal(temporaryPin, input) {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
}
return res, nil
}
func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
_, ok := ctx.Value("SessionId").(string)
@ -284,7 +319,6 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
if err != nil {
return res, err
}
if bytes.Equal(input, temporaryPin) {
res.FlagSet = []uint32{flag_valid_pin}
res.FlagReset = []uint32{flag_pin_mismatch}
@ -362,6 +396,7 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
if !ok {
return res, fmt.Errorf("missing session")
}
if len(input) == 4 {
yob := string(input)
store := h.userdataStore
@ -444,6 +479,14 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
return res, nil
}
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
func (h *Handlers) 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
}
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
@ -527,11 +570,13 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil {
return res, err
}
okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey))
if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
@ -588,7 +633,6 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
var err error
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
date := string(input)
_, err = strconv.Atoi(date)
if err != nil {
@ -694,6 +738,22 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
return res, nil
}
func (h *Handlers) ValidateBlockedNumber(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")
}
blockedNumber := string(input)
err = store.WriteEntry(ctx, sessionId, utils.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
if err != nil {
return res, nil
}
return res, nil
}
// ValidateRecipient validates that the given input is a valid phone number.
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
@ -865,6 +925,22 @@ func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (
return res, nil
}
// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress.
func (h *Handlers) 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, utils.DATA_BLOCKED_NUMBER)
res.Content = string(blockedNumber)
return res, nil
}
// GetSender returns the sessionId (phoneNumber)
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
@ -1102,7 +1178,6 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
if err != nil {
return res, nil
}
err = prefixdb.Put(ctx, []byte("bal"), []byte(voucherBalanceList))
if err != nil {
return res, nil

View File

@ -0,0 +1,80 @@
package utils
import (
"bufio"
"log"
"os"
"strconv"
"strings"
"git.grassecon.net/urdt/ussd/initializers"
)
type AdminStore struct {
filePath string
}
// Creates a new Admin store
func NewAdminStore(filePath string) *AdminStore {
return &AdminStore{filePath: filePath}
}
// Seed initializes a list of phonenumbers with admin privileges
func (as *AdminStore) Seed() error {
var adminNumbers []int64
numbersEnv := initializers.GetEnv("ADMIN_NUMBERS", "")
for _, numStr := range strings.Split(numbersEnv, ",") {
if num, err := strconv.ParseInt(strings.TrimSpace(numStr), 10, 64); err == nil {
adminNumbers = append(adminNumbers, num)
} else {
log.Printf("Skipping invalid number: %s", numStr)
}
}
file, err := os.Create(as.filePath)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, num := range adminNumbers {
_, err := writer.WriteString(strconv.FormatInt(num, 10) + "\n")
if err != nil {
return err
}
}
return writer.Flush()
}
func (as *AdminStore) load() ([]int64, error) {
file, err := os.Open(as.filePath)
if err != nil {
return nil, err
}
defer file.Close()
var numbers []int64
scanner := bufio.NewScanner(file)
for scanner.Scan() {
num, err := strconv.ParseInt(scanner.Text(), 10, 64)
if err != nil {
return nil, err
}
numbers = append(numbers, num)
}
return numbers, scanner.Err()
}
func (as *AdminStore) IsAdmin(phoneNumber int64) (bool, error) {
phoneNumbers, err := as.load()
if err != nil {
return false, err
}
for _, phonenumber := range phoneNumbers {
if phonenumber == phoneNumber {
return true, nil
}
}
return false, nil
}

View File

@ -28,6 +28,7 @@ const (
DATA_ACTIVE_SYM
DATA_TEMPORARY_BAL
DATA_ACTIVE_BAL
DATA_BLOCKED_NUMBER
)
func typToBytes(typ DataTyp) []byte {

View File

@ -1,9 +1,8 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1
LOAD save_familyname 0
RELOAD save_familyname
MOUT back 0
HALT
LOAD save_familyname 0
RELOAD save_familyname
INCMP _ 0
INCMP pin_entry *

View File

@ -2,6 +2,6 @@ MOUT change_pin 1
MOUT reset_pin 2
MOUT back 0
HALT
INCMP _ 0
INCMP my_account 0
INCMP old_pin 1
INCMP enter_other_number 2