From 603936b50c48532370d81c752085c829cdb64501 Mon Sep 17 00:00:00 2001 From: Donovan Date: Mon, 19 Aug 2024 19:13:17 -0500 Subject: [PATCH] user signup sessions --- customer.go | 77 +++++++++++++++---- customer_attributes.go | 10 +-- customer_test.go | 54 ++++++++++++++ hashset/hashset.go | 10 +++ models/models.go | 9 --- models/nkode_policy.go | 21 ++++++ nkode_api.go | 66 +++++++++++++++++ user_signup_session.go | 164 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 381 insertions(+), 30 deletions(-) create mode 100644 models/nkode_policy.go create mode 100644 nkode_api.go create mode 100644 user_signup_session.go diff --git a/customer.go b/customer.go index f0b2733..0f68133 100644 --- a/customer.go +++ b/customer.go @@ -37,42 +37,55 @@ func NewCustomer(keypadSize m.KeypadSize, nkodePolicy m.NKodePolicy) (*Customer, return &customer, nil } -func (c *Customer) AddNewUser(user User) error { - _, exists := c.Users[user.Username] +func (c *Customer) AddNewUser(username string, passcodeIdx []int, userInterface UserInterface) error { + _, exists := c.Users[username] if exists { - return errors.New(fmt.Sprintf("user: %s exists", user.Username)) + return errors.New(fmt.Sprintf("User %s already exists for customer %s exists", username, c.CustomerId.String())) } - c.Users[user.Username] = user + newKeys, err := NewUserCipherKeys(c.Attributes.KeypadSize, c.Attributes.SetVals, c.NKodePolicy.MaxNkodeLen) + if err != nil { + return err + } + encipheredNKode, err := newKeys.EncipherNKode(passcodeIdx, c.Attributes) + if err != nil { + return err + } + newUser := User{ + Username: username, + EncipheredPasscode: *encipheredNKode, + UserKeys: *newKeys, + Interface: userInterface, + } + c.Users[username] = newUser return nil } -func (c *Customer) ValidKeyEntryAndRenew(username string, selectedKeys []int) error { +func (c *Customer) ValidKeyEntry(username string, selectedKeys []int) ([]int, error) { user, exists := c.Users[username] if !exists { - return errors.New(fmt.Sprintf("user %s does not exist for customer %s", username, c.CustomerId.String())) + return nil, errors.New(fmt.Sprintf("user %s does not exist for customer %s", username, c.CustomerId.String())) } validKeys := py.All[int](selectedKeys, func(idx int) bool { - return 0 <= idx && idx < c.Attributes.keypadSize.NumbOfKeys + return 0 <= idx && idx < c.Attributes.KeypadSize.NumbOfKeys }) if !validKeys { - return errors.New(fmt.Sprintf("one or more keys not in range 0-%d", c.Attributes.keypadSize.NumbOfKeys-1)) + return nil, errors.New(fmt.Sprintf("one or more keys not in range 0-%d", c.Attributes.KeypadSize.NumbOfKeys-1)) } presumedAttrIdxVals, err := c.getPresumedAttributeIdxVals(user, selectedKeys) if err != nil { - return err + return nil, err + } + err = c.IsValidNKode(presumedAttrIdxVals) + if err != nil { + return nil, err } - err = user.UserKeys.ValidPassword(user.EncipheredPasscode.Code, presumedAttrIdxVals, c.Attributes) if err != nil { - return err + return nil, err } - if user.Renew { - // renew - } - return nil - + return presumedAttrIdxVals, nil } func (c *Customer) getPresumedAttributeIdxVals(user User, selectedKeys []int) ([]int, error) { @@ -107,5 +120,37 @@ func (c *Customer) IsValidNKode(passcodeAttrIdx []int) error { if nkodeLen < c.NKodePolicy.MinNkodeLen { return errors.New(fmt.Sprintf("NKode length %d is too short. Minimum nKode length is %d", nkodeLen, c.NKodePolicy.MinNkodeLen)) } + + validIdx := py.All[int](passcodeAttrIdx, func(i int) bool { + return i >= 0 && i < c.Attributes.KeypadSize.TotalAttrs() + }) + + if !validIdx { + return errors.New(fmt.Sprintf("One or more idx out of range 0-%d in IsValidNKode", c.Attributes.KeypadSize.TotalAttrs()-1)) + } + passcodeSetVals := make([]uint64, nkodeLen) + var err error + for idx := range passcodeSetVals { + attrVal := c.Attributes.AttrVals[passcodeAttrIdx[idx]] + passcodeSetVals[idx], err = c.Attributes.GetAttrSetVal(attrVal) + if err != nil { + return err + } + } + return nil } + +func (c *Customer) GetLoginInterface(username string) ([]int, error) { + user, exists := c.Users[username] + if !exists { + return nil, errors.New(fmt.Sprintf("can't get login interface for non-existant user %s in customer %s", username, c.CustomerId.String())) + } + err := user.Interface.PartialInterfaceShuffle() + + if err != nil { + return nil, err + } + c.Users[username] = user + return user.Interface.IdxInterface, nil +} diff --git a/customer_attributes.go b/customer_attributes.go index d04bdfe..c301fd2 100644 --- a/customer_attributes.go +++ b/customer_attributes.go @@ -11,7 +11,7 @@ import ( type CustomerAttributes struct { AttrVals []uint64 SetVals []uint64 - keypadSize models.KeypadSize + KeypadSize models.KeypadSize } func NewCustomerAttributes(keypadSize models.KeypadSize) (*CustomerAttributes, error) { @@ -31,17 +31,17 @@ func NewCustomerAttributes(keypadSize models.KeypadSize) (*CustomerAttributes, e customerAttrs := CustomerAttributes{ AttrVals: attrVals, SetVals: setVals, - keypadSize: keypadSize, + KeypadSize: keypadSize, } return &customerAttrs, nil } func (c *CustomerAttributes) Renew() error { - attrVals, errAttr := util.GenerateRandomNonRepeatingUint64(c.keypadSize.TotalAttrs()) + attrVals, errAttr := util.GenerateRandomNonRepeatingUint64(c.KeypadSize.TotalAttrs()) if errAttr != nil { return errAttr } - setVals, errSet := util.GenerateRandomNonRepeatingUint64(c.keypadSize.AttrsPerKey) + setVals, errSet := util.GenerateRandomNonRepeatingUint64(c.KeypadSize.AttrsPerKey) if errSet != nil { return errSet } @@ -69,6 +69,6 @@ func (c *CustomerAttributes) GetAttrSetVal(attrVal uint64) (uint64, error) { if indexOfAttr == -1 { return 0, errors.New(fmt.Sprintf("No attribute %d", attrVal)) } - setIdx := indexOfAttr % c.keypadSize.AttrsPerKey + setIdx := indexOfAttr % c.KeypadSize.AttrsPerKey return c.SetVals[setIdx], nil } diff --git a/customer_test.go b/customer_test.go index 1b0ca85..0aef67d 100644 --- a/customer_test.go +++ b/customer_test.go @@ -1,13 +1,67 @@ package main import ( + "errors" + "fmt" "github.com/stretchr/testify/assert" "go-nkode/models" + "go-nkode/util" "testing" ) +func SelectKeyByAttrIdx(interfaceUser []int, passcodeIdxs []int, keypadSize models.KeypadSize) ([]int, error) { + selectedKeys := make([]int, len(passcodeIdxs)) + for idx := range passcodeIdxs { + attrIdx := util.IndexOf[int](interfaceUser, passcodeIdxs[idx]) + keyNumb := attrIdx / keypadSize.AttrsPerKey + if keyNumb < 0 || keyNumb >= keypadSize.NumbOfKeys { + return nil, errors.New(fmt.Sprintf("index key number: %d out of range 0-%d", keyNumb, keypadSize.NumbOfKeys-1)) + } + selectedKeys[idx] = keyNumb + } + return selectedKeys, nil +} + func TestNewCustomerAttributes(t *testing.T) { keypad := models.KeypadSize{AttrsPerKey: 10, NumbOfKeys: 5} _, nil := NewCustomerAttributes(keypad) assert.NoError(t, nil) } + +func TestCustomer_ValidKeyEntry(t *testing.T) { + keypadSize := models.KeypadSize{AttrsPerKey: 10, NumbOfKeys: 7} + nkodePolicy := models.NewDefaultNKodePolicy() + customer, err := NewCustomer(keypadSize, nkodePolicy) + assert.NoError(t, err) + newUserInterface, err := NewUserInterface(customer.Attributes.KeypadSize) + assert.NoError(t, err) + username := "testing123" + passcodeIdx := []int{0, 1, 2, 3} + err = customer.AddNewUser(username, passcodeIdx, *newUserInterface) + assert.NoError(t, err) + userLoginInterface, err := customer.GetLoginInterface(username) + assert.NoError(t, err) + selectedKeys, err := SelectKeyByAttrIdx(userLoginInterface, passcodeIdx, keypadSize) + assert.NoError(t, err) + validatedPasscode, err := customer.ValidKeyEntry(username, selectedKeys) + assert.NoError(t, err) + assert.Equal(t, len(validatedPasscode), len(passcodeIdx)) + for idx := range validatedPasscode { + assert.Equal(t, validatedPasscode[idx], passcodeIdx[idx]) + } +} + +func TestCustomer_IsValidNKode(t *testing.T) { + keypadSize := models.KeypadSize{AttrsPerKey: 10, NumbOfKeys: 7} + nkodePolicy := models.NewDefaultNKodePolicy() + customer, err := NewCustomer(keypadSize, nkodePolicy) + assert.NoError(t, err) + newUserInterface, err := NewUserInterface(customer.Attributes.KeypadSize) + assert.NoError(t, err) + username := "testing123" + passcodeIdx := []int{0, 1, 2, 3} + err = customer.AddNewUser(username, passcodeIdx, *newUserInterface) + assert.NoError(t, err) + err = customer.IsValidNKode(passcodeIdx) + assert.NoError(t, err) +} diff --git a/hashset/hashset.go b/hashset/hashset.go index a7e15a2..9b0a9b4 100644 --- a/hashset/hashset.go +++ b/hashset/hashset.go @@ -51,3 +51,13 @@ func (s *Set[T]) IsDisjoint(otherSet Set[T]) bool { } return true } + +func (s *Set[T]) Intersect(otherSet Set[T]) Set[T] { + intersect := make(Set[T]) + for val, _ := range *s { + if otherSet.Contains(val) { + intersect.Add(val) + } + } + return intersect +} diff --git a/models/models.go b/models/models.go index 79fd451..60aafc5 100644 --- a/models/models.go +++ b/models/models.go @@ -17,12 +17,3 @@ type EncipheredNKode struct { Code string Mask string } - -type NKodePolicy struct { - MaxNkodeLen int - MinNkodeLen int - DistinctSets int - DistinctAttributes int - LockOut int - Expiration int // seconds, -1 no expiration -} diff --git a/models/nkode_policy.go b/models/nkode_policy.go new file mode 100644 index 0000000..a6662f8 --- /dev/null +++ b/models/nkode_policy.go @@ -0,0 +1,21 @@ +package models + +type NKodePolicy struct { + MaxNkodeLen int + MinNkodeLen int + DistinctSets int + DistinctAttributes int + LockOut int + Expiration int // seconds, -1 no expiration +} + +func NewDefaultNKodePolicy() NKodePolicy { + return NKodePolicy{ + MinNkodeLen: 4, + MaxNkodeLen: 10, + DistinctSets: 0, + DistinctAttributes: 4, + LockOut: 5, + Expiration: -1, + } +} diff --git a/nkode_api.go b/nkode_api.go new file mode 100644 index 0000000..a0b50cc --- /dev/null +++ b/nkode_api.go @@ -0,0 +1,66 @@ +package main + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "go-nkode/models" +) + +type NKodeAPI struct { + Customers map[uuid.UUID]Customer + SignupSessions map[uuid.UUID]UserSignSession +} + +func NewNKodeAPI() NKodeAPI { + return NKodeAPI{ + Customers: make(map[uuid.UUID]Customer), + SignupSessions: make(map[uuid.UUID]UserSignSession), + } +} + +func (n *NKodeAPI) CreateNewCustomer(keypadSize models.KeypadSize, nkodePolicy models.NKodePolicy) (*uuid.UUID, error) { + newCustomer, err := NewCustomer(keypadSize, nkodePolicy) + if err != nil { + return nil, err + } + n.Customers[newCustomer.CustomerId] = *newCustomer + return &newCustomer.CustomerId, nil +} + +func (n *NKodeAPI) GenerateSignupInterface(customerId uuid.UUID) (*uuid.UUID, []int, error) { + customer, exists := n.Customers[customerId] + if !exists { + return nil, nil, errors.New(fmt.Sprintf("customer doesnt exists: %s", customerId.String())) + } + + signupSession, err := NewSignupSession(customer.Attributes.KeypadSize, customer.CustomerId) + if err != nil { + return nil, nil, err + } + n.SignupSessions[signupSession.SessionId] = *signupSession + return &signupSession.SessionId, signupSession.SetInterface, nil +} + +func (n *NKodeAPI) SetNKode(username string, customerId uuid.UUID, keySelection []int, sessionId uuid.UUID) ([]int, error) { + customer, exists := n.Customers[customerId] + if !exists { + return nil, errors.New(fmt.Sprintf("set nkode customer id does not exist %s", customerId.String())) + } + _, exists = customer.Users[username] + if exists { + return nil, errors.New(fmt.Sprintf("user already exists %s", username)) + } + + session, exists := n.SignupSessions[sessionId] + if !exists { + return nil, errors.New(fmt.Sprintf("session id does not exist %s", sessionId.String())) + } + confirmInterface, err := session.SetUserNKode(username, keySelection) + if err != nil { + return nil, err + } + return confirmInterface, nil +} + +func (n *NKodeAPI) ConfirmNKode(username string, customerId uuid.UUID, confirmKeyEntry []int) diff --git a/user_signup_session.go b/user_signup_session.go new file mode 100644 index 0000000..04c4737 --- /dev/null +++ b/user_signup_session.go @@ -0,0 +1,164 @@ +package main + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "go-nkode/hashset" + "go-nkode/models" + py_builtin "go-nkode/py-builtin" + "go-nkode/util" +) + +type UserSignSession struct { + SessionId uuid.UUID + CustomerId uuid.UUID + LoginInterface UserInterface + KeypadSize models.KeypadSize + SetInterface []int + ConfirmInterface []int + SetKeyEntry []int + Username string +} + +func NewSignupSession(keypadSize models.KeypadSize, customerId uuid.UUID) (*UserSignSession, error) { + loginInterface, err := NewUserInterface(keypadSize) + if err != nil { + return nil, err + } + signupInter, err := signupInterface(*loginInterface) + session := UserSignSession{ + SessionId: uuid.New(), + CustomerId: customerId, + LoginInterface: *loginInterface, + SetInterface: signupInter.IdxInterface, + ConfirmInterface: nil, + SetKeyEntry: nil, + Username: "", + KeypadSize: signupInter.KeypadSize, + } + + return &session, nil +} + +func (s *UserSignSession) DeducePasscode(confirmKeyEntry []int) ([]int, error) { + validEntry := py_builtin.All[int](confirmKeyEntry, func(i int) bool { + return 0 <= i && i < s.LoginInterface.KeypadSize.NumbOfKeys + }) + + if !validEntry { + return nil, errors.New(fmt.Sprintf("Invalid Key entry. One or more key index: %#v, not in range 0-%d", confirmKeyEntry, s.LoginInterface.KeypadSize.NumbOfKeys)) + } + + if s.SetInterface == nil { + return nil, errors.New("signup session set interface is nil") + } + + if s.ConfirmInterface == nil { + return nil, errors.New("signup session confirm interface is nil") + } + + if s.SetKeyEntry == nil { + return nil, errors.New("signup session set key entry is nil") + } + + if s.Username == "" { + return nil, errors.New("signup session username is nil") + } + + if len(confirmKeyEntry) == len(s.SetKeyEntry) { + return nil, errors.New(fmt.Sprintf("confirm and set key entry lenght mismatch %d != %d", len(confirmKeyEntry), len(s.SetKeyEntry))) + } + + passcodeLen := len(confirmKeyEntry) + setKeyVals, err := s.getSelectedKeyVals(s.SetKeyEntry, s.SetInterface) + if err != nil { + return nil, err + } + confirmKeyVals, err := s.getSelectedKeyVals(confirmKeyEntry, s.ConfirmInterface) + passcode := make([]int, passcodeLen) + + for idx := 0; idx < passcodeLen; idx++ { + setKey := hashset.NewSetFromSlice[int](setKeyVals[idx]) + confirmKey := hashset.NewSetFromSlice[int](confirmKeyVals[idx]) + intersection := setKey.Intersect(confirmKey) + if intersection.Size() < 1 { + return nil, errors.New(fmt.Sprintf("set and confirm do not intersect at index %d", idx)) + } + if intersection.Size() > 1 { + return nil, errors.New(fmt.Sprintf("set and confirm intersect at more than one point at index %d", idx)) + } + intersectionSlice := intersection.ToSlice() + passcode[idx] = intersectionSlice[0] + } + return passcode, nil +} + +func (s *UserSignSession) SetUserNKode(username string, keySelection []int) ([]int, error) { + validKeySelection := py_builtin.All[int](keySelection, func(i int) bool { + return 0 <= i && i < s.KeypadSize.NumbOfKeys + }) + if !validKeySelection { + return nil, errors.New(fmt.Sprintf("one or key selection is out of range 0-%d", s.KeypadSize.NumbOfKeys-1)) + } + + s.SetKeyEntry = keySelection + s.Username = username + + setInterface := UserInterface{IdxInterface: s.SetInterface, KeypadSize: s.KeypadSize} + err := setInterface.DisperseInterface() + if err != nil { + return nil, err + } + s.ConfirmInterface = setInterface.IdxInterface + return s.ConfirmInterface, nil +} + +func (s *UserSignSession) getSelectedKeyVals(keySelections []int, userInterface []int) ([][]int, error) { + keypadInterface, err := util.ListToMatrix(userInterface, s.KeypadSize.AttrsPerKey) + if err != nil { + return nil, err + } + keyVals := make([][]int, len(keySelections)) + + for idx, keyIdx := range keySelections { + keyVals[idx] = keypadInterface[keyIdx] + } + return keyVals, nil +} + +func signupInterface(baseUserInterface UserInterface) (*UserInterface, error) { + if baseUserInterface.KeypadSize.IsDispersable() { + return nil, errors.New("keypad is dispersable, can't use signupInterface") + } + err := baseUserInterface.RandomShuffle() + if err != nil { + return nil, err + } + interfaceMatrix, err := baseUserInterface.InterfaceMatrix() + if err != nil { + return nil, err + } + attrSetView, err := util.MatrixTranspose(interfaceMatrix) + if err != nil { + return nil, err + } + err = util.FisherYatesShuffle[[]int](&attrSetView) + if err != nil { + return nil, err + } + numbOfKeys := baseUserInterface.KeypadSize.NumbOfKeys + attrSetView = attrSetView[:numbOfKeys] + attrSetView, err = util.MatrixTranspose(attrSetView) + if err != nil { + return nil, err + } + signupUserInterface := UserInterface{ + IdxInterface: util.MatrixToList(attrSetView), + KeypadSize: models.KeypadSize{ + AttrsPerKey: numbOfKeys, + NumbOfKeys: numbOfKeys, + }, + } + return &signupUserInterface, nil +}