From 30f433a85d3351ff1b378089f15970294edfc988 Mon Sep 17 00:00:00 2001
From: joshemb <josh@elixxir.io>
Date: Mon, 22 Aug 2022 09:23:04 -0700
Subject: [PATCH] Have UD store username

---
 bindings/ud.go         | 21 +++++++++++++++++----
 ud/manager.go          | 14 +++++++++++---
 ud/store/facts.go      | 36 ++++++++++++++++++++++++++++++++++--
 ud/store/facts_test.go | 37 ++++++++++++++++++++++++++++---------
 4 files changed, 90 insertions(+), 18 deletions(-)

diff --git a/bindings/ud.go b/bindings/ud.go
index c9f75557f..217fb964b 100644
--- a/bindings/ud.go
+++ b/bindings/ud.go
@@ -208,6 +208,9 @@ func NewOrLoadUd(e2eID int, follower UdNetworkStatus, username string,
 // Parameters:
 //  - e2eID - e2e object ID in the tracker
 //  - follower - network follower func wrapped in UdNetworkStatus
+//  - username - The username this user registered with initially. This should
+//               not be nullable, and be JSON marshalled as retrieved from
+//               UserDiscovery.GetFacts().
 //  - emailFactJson - nullable JSON marshalled email [fact.Fact]
 //  - phoneFactJson - nullable JSON marshalled phone [fact.Fact]
 //  - cert - the TLS certificate for the UD server this call will connect with.
@@ -220,8 +223,9 @@ func NewOrLoadUd(e2eID int, follower UdNetworkStatus, username string,
 //  - address - the IP address of the UD server this call will connect with. You
 //    may use the UD server run by the xx network team by using
 //    E2e.GetUdAddressFromNdf.
-func NewUdManagerFromBackup(e2eID int, follower UdNetworkStatus, emailFactJson,
-	phoneFactJson, cert, contactFile []byte, address string) (*UserDiscovery, error) {
+func NewUdManagerFromBackup(e2eID int, follower UdNetworkStatus,
+	usernameJson, emailFactJson, phoneFactJson,
+	cert, contactFile []byte, address string) (*UserDiscovery, error) {
 
 	// Get user from singleton
 	user, err := e2eTrackerSingleton.get(e2eID)
@@ -229,7 +233,9 @@ func NewUdManagerFromBackup(e2eID int, follower UdNetworkStatus, emailFactJson,
 		return nil, err
 	}
 
-	var email, phone fact.Fact
+	var email, phone, username fact.Fact
+
+	// Parse email if non-nil
 	if emailFactJson != nil {
 		err = json.Unmarshal(emailFactJson, &email)
 		if err != nil {
@@ -237,6 +243,7 @@ func NewUdManagerFromBackup(e2eID int, follower UdNetworkStatus, emailFactJson,
 		}
 	}
 
+	// Parse phone if non-nil
 	if phoneFactJson != nil {
 		err = json.Unmarshal(phoneFactJson, &phone)
 		if err != nil {
@@ -244,13 +251,19 @@ func NewUdManagerFromBackup(e2eID int, follower UdNetworkStatus, emailFactJson,
 		}
 	}
 
+	// Parse username
+	err = json.Unmarshal(usernameJson, &username)
+	if err != nil {
+		return nil, err
+	}
+
 	UdNetworkStatusFn := func() xxdk.Status {
 		return xxdk.Status(follower.UdNetworkStatus())
 	}
 
 	u, err := ud.NewManagerFromBackup(
 		user.api, user.api.GetComms(), UdNetworkStatusFn,
-		email, phone,
+		username, email, phone,
 		cert, contactFile, address)
 	if err != nil {
 		return nil, err
diff --git a/ud/manager.go b/ud/manager.go
index 1f2301050..d431da78c 100644
--- a/ud/manager.go
+++ b/ud/manager.go
@@ -102,6 +102,13 @@ func NewOrLoad(user udE2e, comms Comms, follower udNetworkStatus,
 		return nil, err
 	}
 
+	usernameFact, err := fact.NewFact(fact.Username, username)
+	if err != nil {
+		return nil, err
+	}
+
+	err = m.store.StoreUsername(usernameFact)
+
 	return m, nil
 }
 
@@ -127,7 +134,8 @@ func NewOrLoad(user udE2e, comms Comms, follower udNetworkStatus,
 // Returns
 //  - A Manager object which is registered to the specified UD service.
 func NewManagerFromBackup(user udE2e, comms Comms, follower udNetworkStatus,
-	email, phone fact.Fact, cert, contactFile []byte, address string) (*Manager, error) {
+	username, email, phone fact.Fact,
+	cert, contactFile []byte, address string) (*Manager, error) {
 	jww.INFO.Println("ud.NewManagerFromBackup()")
 	if follower() != xxdk.Running {
 		return nil, errors.New(
@@ -149,7 +157,7 @@ func NewManagerFromBackup(user udE2e, comms Comms, follower udNetworkStatus,
 	}
 
 	// Put any passed in missing facts into store
-	err = m.store.BackUpMissingFacts(email, phone)
+	err = m.store.BackUpMissingFacts(username, email, phone)
 	if err != nil {
 		return nil, errors.WithMessage(err, "Failed to restore UD store "+
 			"from backup")
@@ -180,7 +188,7 @@ func InitStoreFromBackup(kv *versioned.KV,
 	}
 
 	// Put any passed in missing facts into store
-	err = udStore.BackUpMissingFacts(email, phone)
+	err = udStore.BackUpMissingFacts(username, email, phone)
 	if err != nil {
 		return errors.WithMessage(err, "Failed to restore UD store "+
 			"from backup")
diff --git a/ud/store/facts.go b/ud/store/facts.go
index 202d8814b..8ca6c8c37 100644
--- a/ud/store/facts.go
+++ b/ud/store/facts.go
@@ -42,6 +42,22 @@ func (s *Store) RestoreFromBackUp(backupData fact.FactList) error {
 	return s.save()
 }
 
+// StoreUsername forces the storage of a username fact.Fact into the
+// Store's confirmedFacts map. The passed in fact.Fact must be of
+// type fact.Username or this will not store the username.
+func (s *Store) StoreUsername(f fact.Fact) error {
+	s.mux.Lock()
+	defer s.mux.Unlock()
+
+	if f.T != fact.Username {
+		return errors.Errorf("Fact (%s) is not of type username", f.Stringify())
+	}
+
+	s.confirmedFacts[f] = struct{}{}
+
+	return s.saveUnconfirmedFacts()
+}
+
 // StoreUnconfirmedFact stores a fact that has been added to UD but has not been
 // confirmed by the user. It is keyed on the confirmation ID given by UD.
 func (s *Store) StoreUnconfirmedFact(confirmationId string, f fact.Fact) error {
@@ -84,11 +100,11 @@ func (s *Store) ConfirmFact(confirmationId string) error {
 // If you attempt to back up a fact type that has already been backed up,
 // an error will be returned and nothing will be backed up.
 // Otherwise, it adds the fact and returns whether the Store saved successfully.
-func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error {
+func (s *Store) BackUpMissingFacts(username, email, phone fact.Fact) error {
 	s.mux.Lock()
 	defer s.mux.Unlock()
 
-	modifiedEmail, modifiedPhone := false, false
+	modifiedUsername, modifiedEmail, modifiedPhone := false, false, false
 
 	// Handle email if it is not zero (empty string)
 	if !isFactZero(email) {
@@ -106,6 +122,7 @@ func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error {
 		}
 	}
 
+	// Handle phone if it is not an empty string
 	if !isFactZero(phone) {
 		// check if fact is expected type
 		if phone.T != fact.Phone {
@@ -121,6 +138,17 @@ func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error {
 		}
 	}
 
+	if !isFactZero(username) {
+		// Check if fact type is already in map. You should not be able to
+		// overwrite your username.
+		if isFactTypeInMap(fact.Username, s.confirmedFacts) {
+			// If a username exists in memory, return an error
+			return errors.Errorf(factTypeExistsErr, username, fact.Username)
+		} else {
+			modifiedUsername = true
+		}
+	}
+
 	if modifiedPhone || modifiedEmail {
 		if modifiedEmail {
 			s.confirmedFacts[email] = struct{}{}
@@ -130,6 +158,10 @@ func (s *Store) BackUpMissingFacts(email, phone fact.Fact) error {
 			s.confirmedFacts[phone] = struct{}{}
 		}
 
+		if modifiedUsername {
+			s.confirmedFacts[username] = struct{}{}
+		}
+
 		return s.saveConfirmedFacts()
 	}
 
diff --git a/ud/store/facts_test.go b/ud/store/facts_test.go
index 332b5289d..edb141e97 100644
--- a/ud/store/facts_test.go
+++ b/ud/store/facts_test.go
@@ -203,7 +203,12 @@ func TestStore_BackUpMissingFacts(t *testing.T) {
 		T:    fact.Phone,
 	}
 
-	err = expectedStore.BackUpMissingFacts(email, phone)
+	username := fact.Fact{
+		Fact: "admin",
+		T:    fact.Username,
+	}
+
+	err = expectedStore.BackUpMissingFacts(username, email, phone)
 	if err != nil {
 		t.Fatalf("BackUpMissingFacts() produced an error: %v", err)
 	}
@@ -238,18 +243,23 @@ func TestStore_BackUpMissingFacts_DuplicateFactType(t *testing.T) {
 		T:    fact.Phone,
 	}
 
-	err = expectedStore.BackUpMissingFacts(email, phone)
+	username := fact.Fact{
+		Fact: "admin",
+		T:    fact.Username,
+	}
+
+	err = expectedStore.BackUpMissingFacts(username, email, phone)
 	if err != nil {
 		t.Fatalf("BackUpMissingFacts() produced an error: %v", err)
 	}
 
-	err = expectedStore.BackUpMissingFacts(email, fact.Fact{})
+	err = expectedStore.BackUpMissingFacts(username, email, fact.Fact{})
 	if err == nil {
 		t.Fatalf("BackUpMissingFacts() should not allow backing up an "+
 			"email when an email has already been backed up: %v", err)
 	}
 
-	err = expectedStore.BackUpMissingFacts(fact.Fact{}, phone)
+	err = expectedStore.BackUpMissingFacts(username, fact.Fact{}, phone)
 	if err == nil {
 		t.Fatalf("BackUpMissingFacts() should not allow backing up a "+
 			"phone number when a phone number has already been backed up: %v", err)
@@ -272,7 +282,12 @@ func TestStore_GetFacts(t *testing.T) {
 
 	emptyFact := fact.Fact{}
 
-	err = testStore.BackUpMissingFacts(emailFact, emptyFact)
+	username := fact.Fact{
+		Fact: "admin",
+		T:    fact.Username,
+	}
+
+	err = testStore.BackUpMissingFacts(username, emailFact, emptyFact)
 	if err != nil {
 		t.Fatalf("Faild to add fact %v: %v", emailFact, err)
 	}
@@ -282,7 +297,7 @@ func TestStore_GetFacts(t *testing.T) {
 		T:    fact.Phone,
 	}
 
-	err = testStore.BackUpMissingFacts(emptyFact, phoneFact)
+	err = testStore.BackUpMissingFacts(username, emptyFact, phoneFact)
 	if err != nil {
 		t.Fatalf("Faild to add fact %v: %v", phoneFact, err)
 	}
@@ -318,10 +333,14 @@ func TestStore_GetFactStrings(t *testing.T) {
 		Fact: "josh@elixxir.io",
 		T:    fact.Email,
 	}
+	username := fact.Fact{
+		Fact: "admin",
+		T:    fact.Username,
+	}
 
 	emptyFact := fact.Fact{}
 
-	err = testStore.BackUpMissingFacts(emailFact, emptyFact)
+	err = testStore.BackUpMissingFacts(username, emailFact, emptyFact)
 	if err != nil {
 		t.Fatalf("Faild to add fact %v: %v", emailFact, err)
 	}
@@ -331,12 +350,12 @@ func TestStore_GetFactStrings(t *testing.T) {
 		T:    fact.Phone,
 	}
 
-	err = testStore.BackUpMissingFacts(emptyFact, phoneFact)
+	err = testStore.BackUpMissingFacts(emptyFact, emptyFact, phoneFact)
 	if err != nil {
 		t.Fatalf("Faild to add fact %v: %v", phoneFact, err)
 	}
 
-	expectedFacts := []string{emailFact.Stringify(), phoneFact.Stringify()}
+	expectedFacts := []string{username.Stringify(), emailFact.Stringify(), phoneFact.Stringify()}
 
 	receivedFacts := testStore.GetStringifiedFacts()
 	sort.SliceStable(receivedFacts, func(i, j int) bool {
-- 
GitLab