migrate nkode-core
This commit is contained in:
102
entities/customer.go
Normal file
102
entities/customer.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/sqlc"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/utils"
|
||||
"github.com/DonovanKelly/sugar-n-spice/set"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Customer struct {
|
||||
Id CustomerId
|
||||
NKodePolicy NKodePolicy
|
||||
Attributes CustomerAttributes
|
||||
}
|
||||
|
||||
func NewCustomer(nkodePolicy NKodePolicy) (*Customer, error) {
|
||||
customerAttrs, err := NewCustomerAttributes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customer := Customer{
|
||||
Id: CustomerId(uuid.New()),
|
||||
NKodePolicy: nkodePolicy,
|
||||
Attributes: *customerAttrs,
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
func (c *Customer) IsValidNKode(kp KeypadDimension, passcodeAttrIdx []int) error {
|
||||
nkodeLen := len(passcodeAttrIdx)
|
||||
if nkodeLen < c.NKodePolicy.MinNkodeLen || nkodeLen > c.NKodePolicy.MaxNkodeLen {
|
||||
return config.ErrInvalidNKodeLength
|
||||
}
|
||||
|
||||
if validIdx := kp.ValidateAttributeIndices(passcodeAttrIdx); !validIdx {
|
||||
return config.ErrInvalidNKodeIdx
|
||||
}
|
||||
passcodeSetVals := make(set.Set[uint64])
|
||||
passcodeAttrVals := make(set.Set[uint64])
|
||||
attrVals, err := c.Attributes.AttrValsForKp(kp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for idx := 0; idx < nkodeLen; idx++ {
|
||||
attrVal := attrVals[passcodeAttrIdx[idx]]
|
||||
setVal, err := c.Attributes.GetAttrSetVal(attrVal, kp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
passcodeSetVals.Add(setVal)
|
||||
passcodeAttrVals.Add(attrVal)
|
||||
}
|
||||
|
||||
if passcodeSetVals.Size() < c.NKodePolicy.DistinctSets {
|
||||
return config.ErrTooFewDistinctSet
|
||||
}
|
||||
|
||||
if passcodeAttrVals.Size() < c.NKodePolicy.DistinctAttributes {
|
||||
return config.ErrTooFewDistinctAttributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Customer) RenewKeys() ([]uint64, []uint64, error) {
|
||||
oldAttrs := make([]uint64, len(c.Attributes.AttrVals))
|
||||
oldSets := make([]uint64, len(c.Attributes.SetVals))
|
||||
|
||||
copy(oldAttrs, c.Attributes.AttrVals)
|
||||
copy(oldSets, c.Attributes.SetVals)
|
||||
|
||||
if err := c.Attributes.Renew(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
attrsXor, err := security.XorLists(oldAttrs, c.Attributes.AttrVals)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
setXor, err := security.XorLists(oldSets, c.Attributes.SetVals)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return setXor, attrsXor, nil
|
||||
}
|
||||
|
||||
func (c *Customer) ToSqlcCreateCustomerParams() sqlc.CreateCustomerParams {
|
||||
return sqlc.CreateCustomerParams{
|
||||
ID: uuid.UUID(c.Id).String(),
|
||||
MaxNkodeLen: int64(c.NKodePolicy.MaxNkodeLen),
|
||||
MinNkodeLen: int64(c.NKodePolicy.MinNkodeLen),
|
||||
DistinctSets: int64(c.NKodePolicy.DistinctSets),
|
||||
DistinctAttributes: int64(c.NKodePolicy.DistinctAttributes),
|
||||
LockOut: int64(c.NKodePolicy.LockOut),
|
||||
Expiration: int64(c.NKodePolicy.Expiration),
|
||||
AttributeValues: c.Attributes.AttrBytes(),
|
||||
SetValues: c.Attributes.SetBytes(),
|
||||
LastRenew: utils.TimeStamp(),
|
||||
CreatedAt: utils.TimeStamp(),
|
||||
}
|
||||
}
|
||||
94
entities/customer_attributes.go
Normal file
94
entities/customer_attributes.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
"log"
|
||||
)
|
||||
|
||||
type CustomerAttributes struct {
|
||||
AttrVals []uint64
|
||||
SetVals []uint64
|
||||
}
|
||||
|
||||
func NewCustomerAttributes() (*CustomerAttributes, error) {
|
||||
attrVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs())
|
||||
if err != nil {
|
||||
log.Print("unable to generate attribute vals: ", err)
|
||||
return nil, err
|
||||
}
|
||||
setVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey)
|
||||
if err != nil {
|
||||
log.Print("unable to generate set vals: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customerAttrs := CustomerAttributes{
|
||||
AttrVals: attrVals,
|
||||
SetVals: setVals,
|
||||
}
|
||||
return &customerAttrs, nil
|
||||
}
|
||||
|
||||
func NewCustomerAttributesFromBytes(attrBytes []byte, setBytes []byte) CustomerAttributes {
|
||||
return CustomerAttributes{
|
||||
AttrVals: security.ByteArrToUint64Arr(attrBytes),
|
||||
SetVals: security.ByteArrToUint64Arr(setBytes),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) Renew() error {
|
||||
attrVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.AttrVals = attrVals
|
||||
c.SetVals = setVals
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) IndexOfAttr(attrVal uint64) (int, error) {
|
||||
// TODO: should this be mapped instead?
|
||||
return security.IndexOf[uint64](c.AttrVals, attrVal)
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) IndexOfSet(setVal uint64) (int, error) {
|
||||
// TODO: should this be mapped instead?
|
||||
return security.IndexOf[uint64](c.SetVals, setVal)
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) GetAttrSetVal(attrVal uint64, userKeypad KeypadDimension) (uint64, error) {
|
||||
indexOfAttr, err := c.IndexOfAttr(attrVal)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
setIdx := indexOfAttr % userKeypad.AttrsPerKey
|
||||
return c.SetVals[setIdx], nil
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) AttrValsForKp(userKp KeypadDimension) ([]uint64, error) {
|
||||
err := userKp.IsValidKeypadDimension()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.AttrVals[:userKp.TotalAttrs()], nil
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) SetValsForKp(userKp KeypadDimension) ([]uint64, error) {
|
||||
err := userKp.IsValidKeypadDimension()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.SetVals[:userKp.AttrsPerKey], nil
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) AttrBytes() []byte {
|
||||
return security.Uint64ArrToByteArr(c.AttrVals)
|
||||
}
|
||||
|
||||
func (c *CustomerAttributes) SetBytes() []byte {
|
||||
return security.Uint64ArrToByteArr(c.SetVals)
|
||||
}
|
||||
57
entities/customer_test.go
Normal file
57
entities/customer_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCustomer(t *testing.T) {
|
||||
testNewCustomerAttributes(t)
|
||||
testCustomerValidKeyEntry(t)
|
||||
testCustomerIsValidNKode(t)
|
||||
}
|
||||
|
||||
func testNewCustomerAttributes(t *testing.T) {
|
||||
_, nil := NewCustomerAttributes()
|
||||
assert.NoError(t, nil)
|
||||
}
|
||||
|
||||
func testCustomerValidKeyEntry(t *testing.T) {
|
||||
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 9}
|
||||
nkodePolicy := NewDefaultNKodePolicy()
|
||||
customer, err := NewCustomer(nkodePolicy)
|
||||
assert.NoError(t, err)
|
||||
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
|
||||
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
|
||||
assert.NoError(t, err)
|
||||
userEmail := "testing@example.com"
|
||||
passcodeIdx := []int{0, 1, 2, 3}
|
||||
user, err := NewUser(*customer, userEmail, passcodeIdx, *userInterface, kp)
|
||||
assert.NoError(t, err)
|
||||
userLoginInterface, err := user.GetLoginInterface()
|
||||
assert.NoError(t, err)
|
||||
selectedKeys, err := SelectKeyByAttrIdx(userLoginInterface, passcodeIdx, kp)
|
||||
assert.NoError(t, err)
|
||||
validatedPasscode, err := ValidKeyEntry(*user, *customer, selectedKeys)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(validatedPasscode), len(passcodeIdx))
|
||||
for idx := range validatedPasscode {
|
||||
assert.Equal(t, validatedPasscode[idx], passcodeIdx[idx])
|
||||
}
|
||||
}
|
||||
|
||||
func testCustomerIsValidNKode(t *testing.T) {
|
||||
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 7}
|
||||
nkodePolicy := NewDefaultNKodePolicy()
|
||||
customer, err := NewCustomer(nkodePolicy)
|
||||
assert.NoError(t, err)
|
||||
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
|
||||
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
|
||||
assert.NoError(t, err)
|
||||
userEmail := "testing123@example.com"
|
||||
passcodeIdx := []int{0, 1, 2, 3}
|
||||
user, err := NewUser(*customer, userEmail, passcodeIdx, *userInterface, kp)
|
||||
assert.NoError(t, err)
|
||||
err = customer.IsValidNKode(user.Kp, passcodeIdx)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
55
entities/keypad_dimension.go
Normal file
55
entities/keypad_dimension.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
"github.com/DonovanKelly/sugar-n-spice/all"
|
||||
)
|
||||
|
||||
type KeypadDimension struct {
|
||||
AttrsPerKey int `json:"attrs_per_key"`
|
||||
NumbOfKeys int `json:"numb_of_keys"`
|
||||
}
|
||||
|
||||
func (kp *KeypadDimension) TotalAttrs() int {
|
||||
return kp.AttrsPerKey * kp.NumbOfKeys
|
||||
}
|
||||
|
||||
func (kp *KeypadDimension) IsDispersable() bool {
|
||||
return kp.AttrsPerKey <= kp.NumbOfKeys
|
||||
}
|
||||
|
||||
func (kp *KeypadDimension) IsValidKeypadDimension() error {
|
||||
if KeypadMin.AttrsPerKey > kp.AttrsPerKey || KeypadMax.AttrsPerKey < kp.AttrsPerKey || KeypadMin.NumbOfKeys > kp.NumbOfKeys || KeypadMax.NumbOfKeys < kp.NumbOfKeys {
|
||||
return config.ErrInvalidKeypadDimensions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kp *KeypadDimension) ValidKeySelections(selectedKeys []int) bool {
|
||||
return all.All[int](selectedKeys, func(idx int) bool {
|
||||
return 0 <= idx && idx < kp.NumbOfKeys
|
||||
})
|
||||
}
|
||||
|
||||
func (kp *KeypadDimension) ValidateAttributeIndices(attrIndicies []int) bool {
|
||||
return all.All[int](attrIndicies, func(i int) bool {
|
||||
return i >= 0 && i < kp.TotalAttrs()
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
KeypadMax = KeypadDimension{
|
||||
AttrsPerKey: 16,
|
||||
NumbOfKeys: 15,
|
||||
}
|
||||
|
||||
KeypadMin = KeypadDimension{
|
||||
AttrsPerKey: 5,
|
||||
NumbOfKeys: 4,
|
||||
}
|
||||
|
||||
KeypadDefault = KeypadDimension{
|
||||
AttrsPerKey: 14,
|
||||
NumbOfKeys: 7,
|
||||
}
|
||||
)
|
||||
23
entities/keypad_utils.go
Normal file
23
entities/keypad_utils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
)
|
||||
|
||||
func SelectKeyByAttrIdx(interfaceUser []int, passcodeIdxs []int, keypadSize KeypadDimension) ([]int, error) {
|
||||
selectedKeys := make([]int, len(passcodeIdxs))
|
||||
for idx := range passcodeIdxs {
|
||||
attrIdx, err := security.IndexOf[int](interfaceUser, passcodeIdxs[idx])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyNumb := attrIdx / keypadSize.AttrsPerKey
|
||||
if 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
|
||||
}
|
||||
101
entities/models.go
Normal file
101
entities/models.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"net/mail"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type KeySelection []int
|
||||
|
||||
type CustomerId uuid.UUID
|
||||
|
||||
func CustomerIdToString(customerId CustomerId) string {
|
||||
customerUuid := uuid.UUID(customerId)
|
||||
return customerUuid.String()
|
||||
}
|
||||
|
||||
type SessionId uuid.UUID
|
||||
type UserId uuid.UUID
|
||||
|
||||
func UserIdFromString(userId string) UserId {
|
||||
id, err := uuid.Parse(userId)
|
||||
if err != nil {
|
||||
fmt.Errorf("unable to parse user id %+v", err)
|
||||
}
|
||||
return UserId(id)
|
||||
}
|
||||
|
||||
func (s *SessionId) String() string {
|
||||
id := uuid.UUID(*s)
|
||||
return id.String()
|
||||
}
|
||||
|
||||
type UserEmail string
|
||||
|
||||
func ParseEmail(email string) (UserEmail, error) {
|
||||
_, err := mail.ParseAddress(email)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return UserEmail(strings.ToLower(email)), err
|
||||
|
||||
}
|
||||
|
||||
type IdxInterface []int
|
||||
type SvgIdInterface []int
|
||||
|
||||
func SessionIdFromString(sessionId string) (SessionId, error) {
|
||||
id, err := uuid.Parse(sessionId)
|
||||
if err != nil {
|
||||
return SessionId{}, err
|
||||
}
|
||||
|
||||
return SessionId(id), nil
|
||||
}
|
||||
|
||||
type EncipheredNKode struct {
|
||||
Code string
|
||||
Mask string
|
||||
}
|
||||
|
||||
type RGBColor struct {
|
||||
Red int `json:"red"`
|
||||
Green int `json:"green"`
|
||||
Blue int `json:"blue"`
|
||||
}
|
||||
|
||||
var SetColors = []RGBColor{
|
||||
{0, 0, 0}, // Black
|
||||
{255, 0, 0}, // Red
|
||||
{0, 128, 0}, // Dark Green
|
||||
{0, 0, 255}, // Blue
|
||||
{244, 200, 60}, // Yellow
|
||||
{255, 0, 255}, // Magenta
|
||||
{0, 200, 200}, // Cyan
|
||||
{127, 0, 127}, // Purple
|
||||
{232, 92, 13}, // Orange
|
||||
{0, 127, 127}, // Teal
|
||||
{127, 127, 0}, // Olive
|
||||
{127, 0, 0}, // Dark Red
|
||||
{128, 128, 128}, // Gray
|
||||
{228, 102, 102}, // Dark Purple
|
||||
{185, 17, 240}, // Salmon
|
||||
{16, 200, 100}, // Green
|
||||
}
|
||||
|
||||
type SignupResetInterface struct {
|
||||
SessionId string `json:"session_id"`
|
||||
UserIdxInterface IdxInterface `json:"user_interface"`
|
||||
SvgInterface []string `json:"svg_interface"`
|
||||
Colors []RGBColor `json:"colors"`
|
||||
}
|
||||
|
||||
type LoginInterface struct {
|
||||
UserIdxInterface IdxInterface `json:"user_interface"`
|
||||
SvgInterface []string `json:"svg_interface"`
|
||||
AttrsPerKey int `json:"attrs_per_key"`
|
||||
NumbOfKeys int `json:"numb_of_keys"`
|
||||
Colors []RGBColor `json:"colors"`
|
||||
}
|
||||
34
entities/policy.go
Normal file
34
entities/policy.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package entities
|
||||
|
||||
import "git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
|
||||
type NKodePolicy struct {
|
||||
MaxNkodeLen int `json:"max_nkode_len"`
|
||||
MinNkodeLen int `json:"min_nkode_len"`
|
||||
DistinctSets int `json:"distinct_sets"`
|
||||
DistinctAttributes int `json:"distinct_attributes"`
|
||||
LockOut int `json:"lock_out"`
|
||||
Expiration int `json:"expiration"` // seconds, -1 no expiration
|
||||
}
|
||||
|
||||
func NewDefaultNKodePolicy() NKodePolicy {
|
||||
return NKodePolicy{
|
||||
MinNkodeLen: 4,
|
||||
MaxNkodeLen: 4,
|
||||
DistinctSets: 0,
|
||||
DistinctAttributes: 4,
|
||||
LockOut: 5,
|
||||
Expiration: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *NKodePolicy) ValidLength(nkodeLen int) error {
|
||||
|
||||
if nkodeLen < p.MinNkodeLen || nkodeLen > p.MaxNkodeLen {
|
||||
return config.ErrInvalidNKodeLength
|
||||
}
|
||||
// TODO: validate Max > Min
|
||||
// Validate lockout
|
||||
// Add Lockout To User
|
||||
return nil
|
||||
}
|
||||
142
entities/user.go
Normal file
142
entities/user.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id UserId
|
||||
CustomerId CustomerId
|
||||
Email UserEmail
|
||||
EncipheredPasscode EncipheredNKode
|
||||
Kp KeypadDimension
|
||||
CipherKeys UserCipherKeys
|
||||
Interface UserInterface
|
||||
Renew bool
|
||||
RefreshToken string
|
||||
}
|
||||
|
||||
func (u *User) DecipherMask(setVals []uint64, passcodeLen int) ([]uint64, error) {
|
||||
return u.CipherKeys.DecipherMask(u.EncipheredPasscode.Mask, setVals, passcodeLen)
|
||||
}
|
||||
|
||||
func (u *User) RenewKeys(setXor []uint64, attrXor []uint64) error {
|
||||
u.Renew = true
|
||||
var err error
|
||||
u.CipherKeys.SetKey, err = security.XorLists(setXor[:u.Kp.AttrsPerKey], u.CipherKeys.SetKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.CipherKeys.AlphaKey, err = security.XorLists(attrXor[:u.Kp.TotalAttrs()], u.CipherKeys.AlphaKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *User) RefreshPasscode(passcodeAttrIdx []int, customerAttributes CustomerAttributes) error {
|
||||
setVals, err := customerAttributes.SetValsForKp(u.Kp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newKeys, err := NewUserCipherKeys(&u.Kp, setVals, u.CipherKeys.MaxNKodeLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encipheredPasscode, err := newKeys.EncipherNKode(passcodeAttrIdx, customerAttributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.CipherKeys = *newKeys
|
||||
u.EncipheredPasscode = *encipheredPasscode
|
||||
u.Renew = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) GetLoginInterface() ([]int, error) {
|
||||
return u.Interface.IdxInterface, nil
|
||||
}
|
||||
|
||||
func ValidKeyEntry(user User, customer Customer, selectedKeys []int) ([]int, error) {
|
||||
if validKeys := user.Kp.ValidKeySelections(selectedKeys); !validKeys {
|
||||
|
||||
return nil, config.ErrKeyIndexOutOfRange
|
||||
}
|
||||
|
||||
passcodeLen := len(selectedKeys)
|
||||
if err := customer.NKodePolicy.ValidLength(passcodeLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setVals, err := customer.Attributes.SetValsForKp(user.Kp)
|
||||
if err != nil {
|
||||
log.Printf("fatal error in validate key entry;invalid user keypad dimensions for user %s with error %v", user.Email, err)
|
||||
return nil, config.ErrInternalValidKeyEntry
|
||||
}
|
||||
|
||||
passcodeSetVals, err := user.DecipherMask(setVals, passcodeLen)
|
||||
if err != nil {
|
||||
log.Printf("fatal error in validate key entry;something when wrong deciphering mask;user email %s; error %v", user.Email, err)
|
||||
return nil, config.ErrInternalValidKeyEntry
|
||||
}
|
||||
presumedAttrIdxVals := make([]int, passcodeLen)
|
||||
|
||||
for idx := range presumedAttrIdxVals {
|
||||
keyNumb := selectedKeys[idx]
|
||||
setIdx, err := customer.Attributes.IndexOfSet(passcodeSetVals[idx])
|
||||
if err != nil {
|
||||
log.Printf("fatal error in validate key entry;something when wrong getting the IndexOfSet;user email %s; error %v", user.Email, err)
|
||||
return nil, config.ErrInternalValidKeyEntry
|
||||
}
|
||||
selectedAttrIdx, err := user.Interface.GetAttrIdxByKeyNumbSetIdx(setIdx, keyNumb)
|
||||
if err != nil {
|
||||
log.Printf("fatal error in validate key entry;something when wrong getting the GetAttrIdxByKeyNumbSetIdx;user email %s; error %v", user.Email, err)
|
||||
return nil, config.ErrInternalValidKeyEntry
|
||||
}
|
||||
presumedAttrIdxVals[idx] = selectedAttrIdx
|
||||
}
|
||||
err = customer.IsValidNKode(user.Kp, presumedAttrIdxVals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrVals, err := customer.Attributes.AttrValsForKp(user.Kp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = user.CipherKeys.ValidPassword(user.EncipheredPasscode.Code, presumedAttrIdxVals, attrVals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return presumedAttrIdxVals, nil
|
||||
}
|
||||
|
||||
func NewUser(customer Customer, userEmail string, passcodeIdx []int, ui UserInterface, kp KeypadDimension) (*User, error) {
|
||||
_, err := ParseEmail(userEmail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setVals, err := customer.Attributes.SetValsForKp(kp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newKeys, err := NewUserCipherKeys(&kp, setVals, customer.NKodePolicy.MaxNkodeLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encipheredNKode, err := newKeys.EncipherNKode(passcodeIdx, customer.Attributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newUser := User{
|
||||
Id: UserId(uuid.New()),
|
||||
Email: UserEmail(userEmail),
|
||||
EncipheredPasscode: *encipheredNKode,
|
||||
CipherKeys: *newKeys,
|
||||
Interface: ui,
|
||||
Kp: kp,
|
||||
CustomerId: customer.Id,
|
||||
}
|
||||
return &newUser, nil
|
||||
}
|
||||
193
entities/user_cipher_keys.go
Normal file
193
entities/user_cipher_keys.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type UserCipherKeys struct {
|
||||
AlphaKey []uint64
|
||||
SetKey []uint64
|
||||
PassKey []uint64
|
||||
MaskKey []uint64
|
||||
Salt []byte
|
||||
MaxNKodeLen int
|
||||
Kp *KeypadDimension
|
||||
}
|
||||
|
||||
func NewUserCipherKeys(kp *KeypadDimension, setVals []uint64, maxNKodeLen int) (*UserCipherKeys, error) {
|
||||
err := kp.IsValidKeypadDimension()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setKey, err := security.GenerateRandomNonRepeatingUint64(kp.AttrsPerKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setKey, err = security.XorLists(setKey, setVals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
alphaKey, _ := security.GenerateRandomNonRepeatingUint64(kp.TotalAttrs())
|
||||
passKey, _ := security.GenerateRandomNonRepeatingUint64(maxNKodeLen)
|
||||
maskKey, _ := security.GenerateRandomNonRepeatingUint64(maxNKodeLen)
|
||||
salt, _ := security.RandomBytes(10)
|
||||
userCipherKeys := UserCipherKeys{
|
||||
AlphaKey: alphaKey,
|
||||
PassKey: passKey,
|
||||
MaskKey: maskKey,
|
||||
SetKey: setKey,
|
||||
Salt: salt,
|
||||
MaxNKodeLen: maxNKodeLen,
|
||||
Kp: kp,
|
||||
}
|
||||
return &userCipherKeys, nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) PadUserMask(userMask []uint64, setVals []uint64) ([]uint64, error) {
|
||||
if len(userMask) > u.MaxNKodeLen {
|
||||
return nil, config.ErrUserMaskTooLong
|
||||
}
|
||||
paddedUserMask := make([]uint64, len(userMask))
|
||||
copy(paddedUserMask, userMask)
|
||||
for i := len(userMask); i < u.MaxNKodeLen; i++ {
|
||||
paddedUserMask = append(paddedUserMask, setVals[i%len(setVals)])
|
||||
}
|
||||
return paddedUserMask, nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) ValidPassword(hashedPassword string, passcodeAttrIdx []int, attrVals []uint64) error {
|
||||
hashBytes := []byte(hashedPassword)
|
||||
passcodeCipher := u.encipherCode(passcodeAttrIdx, attrVals)
|
||||
passwordDigest := u.saltAndDigest(passcodeCipher)
|
||||
err := bcrypt.CompareHashAndPassword(hashBytes, passwordDigest)
|
||||
if err != nil {
|
||||
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||
return config.ErrInvalidNKode
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) EncipherSaltHashCode(passcodeAttrIdx []int, attrVals []uint64) (string, error) {
|
||||
passcodeCipher := u.encipherCode(passcodeAttrIdx, attrVals)
|
||||
|
||||
passcodeDigest := u.saltAndDigest(passcodeCipher)
|
||||
passcodeBytes, err := u.hashPasscode(passcodeDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(passcodeBytes), nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) encipherCode(passcodeAttrIdx []int, attrVals []uint64) []uint64 {
|
||||
passcodeLen := len(passcodeAttrIdx)
|
||||
|
||||
passcodeCipher := make([]uint64, u.MaxNKodeLen)
|
||||
for idx := 0; idx < passcodeLen; idx++ {
|
||||
attrIdx := passcodeAttrIdx[idx]
|
||||
alpha := u.AlphaKey[attrIdx]
|
||||
attrVal := attrVals[attrIdx]
|
||||
pass := u.PassKey[idx]
|
||||
passcodeCipher[idx] = alpha ^ pass ^ attrVal
|
||||
}
|
||||
return passcodeCipher
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) saltAndDigest(passcode []uint64) []byte {
|
||||
passcodeBytes := security.Uint64ArrToByteArr(passcode)
|
||||
passcodeBytes = append(passcodeBytes, u.Salt...)
|
||||
h := sha256.New()
|
||||
h.Write(passcodeBytes)
|
||||
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) hashPasscode(passcodeDigest []byte) ([]byte, error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword(passcodeDigest, bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hashedPassword, nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) EncipherMask(passcodeSet []uint64, customerAttrs CustomerAttributes, userKp KeypadDimension) (string, error) {
|
||||
setVals, err := customerAttrs.SetValsForKp(userKp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
paddedPasscodeSets, err := u.PadUserMask(passcodeSet, setVals)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cipheredMask := make([]uint64, len(paddedPasscodeSets))
|
||||
for idx := range paddedPasscodeSets {
|
||||
setIdx, err := customerAttrs.IndexOfSet(paddedPasscodeSets[idx])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setKeyVal := u.SetKey[setIdx]
|
||||
maskKeyVal := u.MaskKey[idx]
|
||||
setVal := paddedPasscodeSets[idx]
|
||||
cipheredMask[idx] = setKeyVal ^ maskKeyVal ^ setVal
|
||||
}
|
||||
mask := security.EncodeBase64Str(cipheredMask)
|
||||
return mask, nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) DecipherMask(mask string, setVals []uint64, passcodeLen int) ([]uint64, error) {
|
||||
decodedMask, err := security.DecodeBase64Str(mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decipheredMask, err := security.XorLists(decodedMask, u.MaskKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setKeyRandComponent, err := security.XorLists(setVals, u.SetKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
passcodeSet := make([]uint64, passcodeLen)
|
||||
for idx, setCipher := range decipheredMask[:passcodeLen] {
|
||||
setIdx, err := security.IndexOf(setKeyRandComponent, setCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
passcodeSet[idx] = setVals[setIdx]
|
||||
}
|
||||
return passcodeSet, nil
|
||||
}
|
||||
|
||||
func (u *UserCipherKeys) EncipherNKode(passcodeAttrIdx []int, customerAttrs CustomerAttributes) (*EncipheredNKode, error) {
|
||||
attrVals, err := customerAttrs.AttrValsForKp(*u.Kp)
|
||||
code, err := u.EncipherSaltHashCode(passcodeAttrIdx, attrVals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
passcodeSet := make([]uint64, len(passcodeAttrIdx))
|
||||
|
||||
for idx := range passcodeSet {
|
||||
passcodeAttr := attrVals[passcodeAttrIdx[idx]]
|
||||
passcodeSet[idx], err = customerAttrs.GetAttrSetVal(passcodeAttr, *u.Kp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
mask, err := u.EncipherMask(passcodeSet, customerAttrs, *u.Kp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encipheredCode := EncipheredNKode{
|
||||
Code: code,
|
||||
Mask: mask,
|
||||
}
|
||||
return &encipheredCode, nil
|
||||
}
|
||||
185
entities/user_interface.go
Normal file
185
entities/user_interface.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
"github.com/DonovanKelly/sugar-n-spice/set"
|
||||
"log"
|
||||
)
|
||||
|
||||
type UserInterface struct {
|
||||
IdxInterface IdxInterface
|
||||
SvgId SvgIdInterface
|
||||
Kp *KeypadDimension
|
||||
}
|
||||
|
||||
func NewUserInterface(kp *KeypadDimension, svgId SvgIdInterface) (*UserInterface, error) {
|
||||
idxInterface := security.IdentityArray(kp.TotalAttrs())
|
||||
userInterface := UserInterface{
|
||||
IdxInterface: idxInterface,
|
||||
SvgId: svgId,
|
||||
Kp: kp,
|
||||
}
|
||||
if err := userInterface.RandomShuffle(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &userInterface, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) RandomShuffle() error {
|
||||
err := u.shuffleKeys()
|
||||
|
||||
keypadView, err := u.InterfaceMatrix()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setView, err := security.MatrixTranspose(keypadView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for idx, set := range setView {
|
||||
err := security.FisherYatesShuffle(&set)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
setView[idx] = set
|
||||
}
|
||||
|
||||
keypadView, err = security.MatrixTranspose(setView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.IdxInterface = security.MatrixToList(keypadView)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) InterfaceMatrix() ([][]int, error) {
|
||||
return security.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetViewMatrix() ([][]int, error) {
|
||||
keypadView, err := u.InterfaceMatrix()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return security.MatrixTranspose(keypadView)
|
||||
}
|
||||
|
||||
func (u *UserInterface) DisperseInterface() error {
|
||||
if !u.Kp.IsDispersable() {
|
||||
return config.ErrInterfaceNotDispersible
|
||||
}
|
||||
|
||||
err := u.shuffleKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = u.randomAttributeRotation()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) shuffleKeys() error {
|
||||
userInterfaceMatrix, err := security.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = security.FisherYatesShuffle[[]int](&userInterfaceMatrix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.IdxInterface = security.MatrixToList(userInterfaceMatrix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) randomAttributeRotation() error {
|
||||
userInterface, err := u.InterfaceMatrix()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transposeUserInterface, err := security.MatrixTranspose(userInterface)
|
||||
|
||||
attrRotation, err := security.RandomPermutation(len(transposeUserInterface))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for idx, attrSet := range transposeUserInterface {
|
||||
rotation := attrRotation[idx]
|
||||
transposeUserInterface[idx] = append(attrSet[rotation:], attrSet[:rotation]...)
|
||||
}
|
||||
userInterface, err = security.MatrixTranspose(transposeUserInterface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.IdxInterface = security.MatrixToList(userInterface)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) AttributeAdjacencyGraph() (map[int]set.Set[int], error) {
|
||||
interfaceKeypad, err := u.InterfaceMatrix()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
graph := make(map[int]set.Set[int])
|
||||
|
||||
for _, key := range interfaceKeypad {
|
||||
keySet := set.NewSetFromSlice(key)
|
||||
for _, attr := range key {
|
||||
attrAdjacency := keySet.Copy()
|
||||
attrAdjacency.Remove(attr)
|
||||
graph[attr] = attrAdjacency
|
||||
}
|
||||
}
|
||||
return graph, nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) LoginShuffle() error {
|
||||
if err := u.shuffleKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
keypadSet1, err := u.InterfaceMatrix()
|
||||
if err = u.shuffleKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
keypadSet2, err := u.InterfaceMatrix()
|
||||
numbOfSelectedSets := u.Kp.AttrsPerKey / 2
|
||||
setIdxs, err := security.RandomPermutation(u.Kp.AttrsPerKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selectedSets := set.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets])
|
||||
|
||||
for keyIdx, key := range keypadSet1 {
|
||||
for idx := range key {
|
||||
if selectedSets.Contains(idx) {
|
||||
keypadSet1[keyIdx][idx] = keypadSet2[keyIdx][idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u.IdxInterface = security.MatrixToList(keypadSet1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserInterface) GetAttrIdxByKeyNumbSetIdx(setIdx int, keyNumb int) (int, error) {
|
||||
if keyNumb < 0 || u.Kp.NumbOfKeys <= keyNumb {
|
||||
log.Printf("keyNumb %d is out of range 0-%d", keyNumb, u.Kp.NumbOfKeys)
|
||||
return -1, config.ErrKeyIndexOutOfRange
|
||||
}
|
||||
|
||||
if setIdx < 0 || u.Kp.AttrsPerKey <= setIdx {
|
||||
log.Printf("setIdx %d is out of range 0-%d", setIdx, u.Kp.AttrsPerKey)
|
||||
return -1, config.ErrAttributeIndexOutOfRange
|
||||
}
|
||||
keypadView, err := u.InterfaceMatrix()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return keypadView[keyNumb][setIdx], nil
|
||||
}
|
||||
201
entities/user_signup_session.go
Normal file
201
entities/user_signup_session.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/config"
|
||||
"git.infra.nkode.tech/dkelly/nkode-core/security"
|
||||
"github.com/DonovanKelly/sugar-n-spice/all"
|
||||
"github.com/DonovanKelly/sugar-n-spice/set"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type UserSignSession struct {
|
||||
Id SessionId
|
||||
CustomerId CustomerId
|
||||
LoginUserInterface UserInterface
|
||||
Kp KeypadDimension
|
||||
SetIdxInterface IdxInterface
|
||||
ConfirmIdxInterface IdxInterface
|
||||
SetKeySelection KeySelection
|
||||
UserEmail UserEmail
|
||||
Reset bool
|
||||
Expire int
|
||||
Colors []RGBColor
|
||||
}
|
||||
|
||||
func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId CustomerId, svgInterface SvgIdInterface, reset bool) (*UserSignSession, error) {
|
||||
loginInterface, err := NewUserInterface(&kp, svgInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signupInterface, colors, err := signupInterface(*loginInterface, kp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := UserSignSession{
|
||||
Id: SessionId(uuid.New()),
|
||||
CustomerId: customerId,
|
||||
LoginUserInterface: *loginInterface,
|
||||
SetIdxInterface: signupInterface.IdxInterface,
|
||||
ConfirmIdxInterface: nil,
|
||||
SetKeySelection: nil,
|
||||
UserEmail: userEmail,
|
||||
Kp: kp,
|
||||
Reset: reset,
|
||||
Colors: colors,
|
||||
}
|
||||
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, error) {
|
||||
validEntry := all.All[int](confirmKeyEntry, func(i int) bool {
|
||||
return 0 <= i && i < s.Kp.NumbOfKeys
|
||||
})
|
||||
|
||||
if !validEntry {
|
||||
log.Printf("Invalid Key entry. One or more key index: %#v, not in range 0-%d", confirmKeyEntry, s.Kp.NumbOfKeys)
|
||||
return nil, config.ErrKeyIndexOutOfRange
|
||||
}
|
||||
|
||||
if s.SetIdxInterface == nil {
|
||||
log.Print("signup session set interface is nil")
|
||||
return nil, config.ErrIncompleteUserSignupSession
|
||||
}
|
||||
|
||||
if s.ConfirmIdxInterface == nil {
|
||||
log.Print("signup session confirm interface is nil")
|
||||
return nil, config.ErrIncompleteUserSignupSession
|
||||
}
|
||||
|
||||
if s.SetKeySelection == nil {
|
||||
log.Print("signup session set key entry is nil")
|
||||
return nil, config.ErrIncompleteUserSignupSession
|
||||
}
|
||||
|
||||
if s.UserEmail == "" {
|
||||
log.Print("signup session username is nil")
|
||||
return nil, config.ErrIncompleteUserSignupSession
|
||||
}
|
||||
|
||||
if len(confirmKeyEntry) != len(s.SetKeySelection) {
|
||||
log.Printf("confirm and set key entry length mismatch %d != %d", len(confirmKeyEntry), len(s.SetKeySelection))
|
||||
return nil, config.ErrSetConfirmSignupMismatch
|
||||
}
|
||||
|
||||
passcodeLen := len(confirmKeyEntry)
|
||||
setKeyVals, err := s.getSelectedKeyVals(s.SetKeySelection, s.SetIdxInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
confirmKeyVals, err := s.getSelectedKeyVals(confirmKeyEntry, s.ConfirmIdxInterface)
|
||||
passcode := make([]int, passcodeLen)
|
||||
|
||||
for idx := 0; idx < passcodeLen; idx++ {
|
||||
setKey := set.NewSetFromSlice[int](setKeyVals[idx])
|
||||
confirmKey := set.NewSetFromSlice[int](confirmKeyVals[idx])
|
||||
intersection := setKey.Intersect(confirmKey)
|
||||
if intersection.Size() < 1 {
|
||||
log.Printf("set and confirm do not intersect at index %d", idx)
|
||||
return nil, config.ErrSetConfirmSignupMismatch
|
||||
}
|
||||
if intersection.Size() > 1 {
|
||||
log.Printf("set and confirm intersect at more than one point at index %d", idx)
|
||||
return nil, config.ErrSetConfirmSignupMismatch
|
||||
}
|
||||
intersectionSlice := intersection.ToSlice()
|
||||
passcode[idx] = intersectionSlice[0]
|
||||
}
|
||||
return passcode, nil
|
||||
}
|
||||
|
||||
func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface, error) {
|
||||
validKeySelection := all.All[int](keySelection, func(i int) bool {
|
||||
return 0 <= i && i < s.Kp.NumbOfKeys
|
||||
})
|
||||
if !validKeySelection {
|
||||
log.Printf("one or key selection is out of range 0-%d", s.Kp.NumbOfKeys-1)
|
||||
return nil, config.ErrKeyIndexOutOfRange
|
||||
}
|
||||
|
||||
s.SetKeySelection = keySelection
|
||||
setKp := s.SignupKeypad()
|
||||
setInterface := UserInterface{IdxInterface: s.SetIdxInterface, Kp: &setKp}
|
||||
err := setInterface.DisperseInterface()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.ConfirmIdxInterface = setInterface.IdxInterface
|
||||
return s.ConfirmIdxInterface, nil
|
||||
}
|
||||
|
||||
func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInterface []int) ([][]int, error) {
|
||||
signupKp := s.SignupKeypad()
|
||||
keypadInterface, err := security.ListToMatrix(userInterface, signupKp.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, kp KeypadDimension) (*UserInterface, []RGBColor, error) {
|
||||
// This method randomly drops sets from the base user interface so it is a square and dispersable matrix
|
||||
if kp.IsDispersable() {
|
||||
return nil, nil, config.ErrKeypadIsNotDispersible
|
||||
}
|
||||
err := baseUserInterface.RandomShuffle()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// attributes are arranged by key interfaceMatrix
|
||||
interfaceMatrix, err := baseUserInterface.InterfaceMatrix()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// attributes are arranged by set
|
||||
attrSetView, err := security.MatrixTranspose(interfaceMatrix)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
setIdxs := security.IdentityArray(kp.AttrsPerKey)
|
||||
if err := security.FisherYatesShuffle[int](&setIdxs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
setIdxs = setIdxs[:kp.NumbOfKeys]
|
||||
sort.Ints(setIdxs)
|
||||
selectedSets := make([][]int, kp.NumbOfKeys)
|
||||
selectedColors := make([]RGBColor, kp.NumbOfKeys)
|
||||
|
||||
for idx, setIdx := range setIdxs {
|
||||
selectedSets[idx] = attrSetView[setIdx]
|
||||
selectedColors[idx] = SetColors[setIdx]
|
||||
}
|
||||
// convert set view back into key view
|
||||
selectedSets, err = security.MatrixTranspose(selectedSets)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
signupUserInterface := UserInterface{
|
||||
IdxInterface: security.MatrixToList(selectedSets),
|
||||
Kp: &KeypadDimension{
|
||||
AttrsPerKey: kp.NumbOfKeys,
|
||||
NumbOfKeys: kp.NumbOfKeys,
|
||||
},
|
||||
}
|
||||
return &signupUserInterface, selectedColors, nil
|
||||
}
|
||||
|
||||
func (s *UserSignSession) SignupKeypad() KeypadDimension {
|
||||
return KeypadDimension{
|
||||
AttrsPerKey: s.Kp.NumbOfKeys,
|
||||
NumbOfKeys: s.Kp.NumbOfKeys,
|
||||
}
|
||||
}
|
||||
133
entities/user_test.go
Normal file
133
entities/user_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"github.com/DonovanKelly/sugar-n-spice/all"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserCipherKeys_EncipherSaltHashCode(t *testing.T) {
|
||||
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 8}
|
||||
maxNKodeLen := 10
|
||||
customerAttrs, err := NewCustomerAttributes()
|
||||
assert.NoError(t, err)
|
||||
setVals, err := customerAttrs.SetValsForKp(kp)
|
||||
assert.NoError(t, err)
|
||||
attrVals, err := customerAttrs.AttrValsForKp(kp)
|
||||
assert.NoError(t, err)
|
||||
newUser, err := NewUserCipherKeys(&kp, setVals, maxNKodeLen)
|
||||
assert.NoError(t, err)
|
||||
passcodeIdx := []int{0, 1, 2, 3}
|
||||
encipher0, err := newUser.EncipherSaltHashCode(passcodeIdx, attrVals)
|
||||
assert.NoError(t, err)
|
||||
err = newUser.ValidPassword(encipher0, passcodeIdx, attrVals)
|
||||
assert.NoError(t, err)
|
||||
|
||||
passcodeIdxInvalid := []int{1, 0, 3, 2}
|
||||
assert.NoError(t, err)
|
||||
err = newUser.ValidPassword(encipher0, passcodeIdxInvalid, attrVals)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUserCipherKeys_EncipherDecipherMask(t *testing.T) {
|
||||
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 8}
|
||||
maxNKodeLen := 10
|
||||
|
||||
customerAttrs, err := NewCustomerAttributes()
|
||||
assert.NoError(t, err)
|
||||
setVals, err := customerAttrs.SetValsForKp(kp)
|
||||
assert.NoError(t, err)
|
||||
attrVals, err := customerAttrs.AttrValsForKp(kp)
|
||||
assert.NoError(t, err)
|
||||
newUser, err := NewUserCipherKeys(&kp, setVals, maxNKodeLen)
|
||||
assert.NoError(t, err)
|
||||
passcodeIdx := []int{0, 1, 2, 3}
|
||||
originalSetVals := make([]uint64, len(passcodeIdx))
|
||||
|
||||
for idx, val := range passcodeIdx {
|
||||
attr := attrVals[val]
|
||||
originalSetVals[idx], err = customerAttrs.GetAttrSetVal(attr, kp)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
encipheredCode, err := newUser.EncipherNKode(passcodeIdx, *customerAttrs)
|
||||
assert.NoError(t, err)
|
||||
passcodeSetVals, err := newUser.DecipherMask(encipheredCode.Mask, setVals, len(passcodeIdx))
|
||||
assert.NoError(t, err)
|
||||
|
||||
for idx, setVal := range passcodeSetVals {
|
||||
assert.Equal(t, setVal, originalSetVals[idx])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserInterface_RandomShuffle(t *testing.T) {
|
||||
kp := KeypadDimension{
|
||||
AttrsPerKey: 10,
|
||||
NumbOfKeys: 8,
|
||||
}
|
||||
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
|
||||
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
|
||||
assert.NoError(t, err)
|
||||
userInterfaceCopy := make([]int, len(userInterface.IdxInterface))
|
||||
copy(userInterfaceCopy, userInterface.IdxInterface)
|
||||
|
||||
err = userInterface.RandomShuffle()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(userInterface.IdxInterface), len(userInterfaceCopy))
|
||||
equalCount := 0
|
||||
for idx, val := range userInterface.IdxInterface {
|
||||
if val == userInterfaceCopy[idx] {
|
||||
equalCount++
|
||||
}
|
||||
}
|
||||
assert.NotEqual(t, equalCount, len(userInterface.IdxInterface))
|
||||
}
|
||||
|
||||
func TestUserInterface_DisperseInterface(t *testing.T) {
|
||||
|
||||
for idx := 0; idx < 10000; idx++ {
|
||||
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
|
||||
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
|
||||
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
|
||||
assert.NoError(t, err)
|
||||
preDispersion, err := userInterface.AttributeAdjacencyGraph()
|
||||
assert.NoError(t, err)
|
||||
err = userInterface.DisperseInterface()
|
||||
assert.NoError(t, err)
|
||||
postDispersion, err := userInterface.AttributeAdjacencyGraph()
|
||||
assert.Equal(t, len(postDispersion), len(preDispersion))
|
||||
for attr, adjAttrs := range preDispersion {
|
||||
postAdjAttrs := postDispersion[attr]
|
||||
assert.Equal(t, adjAttrs.Size(), postAdjAttrs.Size())
|
||||
assert.True(t, adjAttrs.IsDisjoint(postAdjAttrs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserInterface_PartialInterfaceShuffle(t *testing.T) {
|
||||
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
|
||||
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs())
|
||||
userInterface, err := NewUserInterface(&kp, mockSvgInterface)
|
||||
assert.NoError(t, err)
|
||||
preShuffle := userInterface.IdxInterface
|
||||
err = userInterface.LoginShuffle()
|
||||
assert.NoError(t, err)
|
||||
postShuffle := userInterface.IdxInterface
|
||||
|
||||
shuffleCompare := make([]bool, len(postShuffle))
|
||||
for idx, val := range preShuffle {
|
||||
shuffleCompare[idx] = val == postShuffle[idx]
|
||||
}
|
||||
|
||||
allTrue := all.All[bool](shuffleCompare, func(n bool) bool {
|
||||
return n == true
|
||||
})
|
||||
assert.False(t, allTrue)
|
||||
|
||||
allFalse := all.All[bool](shuffleCompare, func(n bool) bool {
|
||||
return n == false
|
||||
})
|
||||
|
||||
assert.False(t, allFalse)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user