Repository: firebase/firebase-admin-go
Pull request #573 (2023-07-24 20:52:43)

feat(auth): Add `TotpInfo` field to `UserRecord`
Original [PR](https://github.com/firebase/firebase-admin-go/pull/558) based off `master`
This is based off `dev` branch. 

davidbudnick: Good Afternoon Team 👋🏻 

Any status updates on getting this MR merged in?

Thanks!

Patch:
diff --git a/auth/user_mgt.go b/auth/user_mgt.go
index 338a976..601c1e0 100644
--- a/auth/user_mgt.go
+++ b/auth/user_mgt.go
@@ -42,6 +42,7 @@ const (
 	createUserMethod           = "createUser"
 	updateUserMethod           = "updateUser"
 	phoneMultiFactorID         = "phone"
+	totpMultiFactorID          = "totp"
 )
 
 // 'REDACTED', encoded as a base64 string.
@@ -62,24 +63,37 @@ type UserInfo struct {
 
 // multiFactorInfoResponse describes the `mfaInfo` of the user record API response
 type multiFactorInfoResponse struct {
-	MFAEnrollmentID string `json:"mfaEnrollmentId,omitempty"`
-	DisplayName     string `json:"displayName,omitempty"`
-	PhoneInfo       string `json:"phoneInfo,omitempty"`
-	EnrolledAt      string `json:"enrolledAt,omitempty"`
+	MFAEnrollmentID string    `json:"mfaEnrollmentId,omitempty"`
+	DisplayName     string    `json:"displayName,omitempty"`
+	PhoneInfo       string    `json:"phoneInfo,omitempty"`
+	TOTPInfo        *TOTPInfo `json:"totpInfo,omitempty"`
+	EnrolledAt      string    `json:"enrolledAt,omitempty"`
 }
 
+// TOTPInfo describes a user enrolled second TOTP factor.
+type TOTPInfo struct{}
+
+// PhoneMultiFactorInfo describes a user enrolled in SMS second factor.
+type PhoneMultiFactorInfo struct {
+	PhoneNumber string
+}
+
+// TOTPMultiFactorInfo describes a user enrolled in TOTP second factor.
+type TOTPMultiFactorInfo struct{}
+
 type multiFactorEnrollments struct {
 	Enrollments []*multiFactorInfoResponse `json:"enrollments"`
 }
 
 // MultiFactorInfo describes a user enrolled second phone factor.
-// TODO : convert PhoneNumber to PhoneMultiFactorInfo struct
 type MultiFactorInfo struct {
 	UID                 string
 	DisplayName         string
 	EnrollmentTimestamp int64
 	FactorID            string
-	PhoneNumber         string
+	PhoneNumber         string // Deprecated: Use PhoneMultiFactorInfo instead
+	Phone               *PhoneMultiFactorInfo
+	TOTP                *TOTPMultiFactorInfo
 }
 
 // MultiFactorSettings describes the multi-factor related user settings.
@@ -177,12 +191,17 @@ func convertMultiFactorInfoToServerFormat(mfaInfo MultiFactorInfo) (multiFactorI
 	if mfaInfo.UID != "" {
 		authFactorInfo.MFAEnrollmentID = mfaInfo.UID
 	}
-	if mfaInfo.FactorID == phoneMultiFactorID {
-		authFactorInfo.PhoneInfo = mfaInfo.PhoneNumber
-		return authFactorInfo, nil
+
+	switch mfaInfo.FactorID {
+	case phoneMultiFactorID:
+		authFactorInfo.PhoneInfo = mfaInfo.Phone.PhoneNumber
+	case totpMultiFactorID:
+		authFactorInfo.TOTPInfo = (*TOTPInfo)(mfaInfo.TOTP)
+	default:
+		out, _ := json.Marshal(mfaInfo)
+		return multiFactorInfoResponse{}, fmt.Errorf("unsupported second factor %s provided", string(out))
 	}
-	out, _ := json.Marshal(mfaInfo)
-	return multiFactorInfoResponse{}, fmt.Errorf("unsupported second factor %s provided", string(out))
+	return authFactorInfo, nil
 }
 
 func (u *UserToCreate) validatedRequest() (map[string]interface{}, error) {
@@ -338,8 +357,7 @@ func (u *UserToUpdate) validatedRequest() (map[string]interface{}, error) {
 			if err != nil {
 				return nil, err
 			}
-
-			// https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/update
+			// Request body ref: https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/update
 			req["mfa"] = multiFactorEnrollments{mfaInfo}
 		} else {
 			req[k] = v
@@ -679,8 +697,24 @@ func validateAndFormatMfaSettings(mfaSettings MultiFactorSettings, methodType st
 			return nil, fmt.Errorf("the second factor \"displayName\" for \"%s\" must be a valid non-empty string", multiFactorInfo.DisplayName)
 		}
 		if multiFactorInfo.FactorID == phoneMultiFactorID {
-			if err := validatePhone(multiFactorInfo.PhoneNumber); err != nil {
-				return nil, fmt.Errorf("the second factor \"phoneNumber\" for \"%s\" must be a non-empty E.164 standard compliant identifier string", multiFactorInfo.PhoneNumber)
+			if multiFactorInfo.Phone != nil {
+				// If PhoneMultiFactorInfo is provided, validate its PhoneNumber field
+				if err := validatePhone(multiFactorInfo.Phone.PhoneNumber); err != nil {
+					return nil, fmt.Errorf("the second factor \"phoneNumber\" for \"%s\" must be a non-empty E.164 standard compliant identifier string", multiFactorInfo.Phone.PhoneNumber)
+				}
+				// No need for the else here since we are returning from the function
+			} else if multiFactorInfo.PhoneNumber != "" {
+				// PhoneMultiFactorInfo is nil, check the deprecated PhoneNumber field
+				if err := validatePhone(multiFactorInfo.PhoneNumber); err != nil {
+					return nil, fmt.Errorf("the second factor \"phoneNumber\" for \"%s\" must be a non-empty E.164 standard compliant identifier string", multiFactorInfo.PhoneNumber)
+				}
+				// The PhoneNumber field is deprecated, set it in PhoneMultiFactorInfo and inform about the deprecation.
+				multiFactorInfo.Phone = &PhoneMultiFactorInfo{
+					PhoneNumber: multiFactorInfo.PhoneNumber,
+				}
+			} else {
+				// Both PhoneMultiFactorInfo and deprecated PhoneNumber are missing.
+				return nil, fmt.Errorf("\"PhoneMultiFactorInfo\" must be defined")
 			}
 		}
 		obj, err := convertMultiFactorInfoToServerFormat(*multiFactorInfo)
@@ -1079,17 +1113,28 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
 			enrollmentTimestamp = t.Unix() * 1000
 		}
 
-		if factor.PhoneInfo == "" {
+		if factor.PhoneInfo != "" {
+			enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
+				UID:                 factor.MFAEnrollmentID,
+				DisplayName:         factor.DisplayName,
+				EnrollmentTimestamp: enrollmentTimestamp,
+				FactorID:            phoneMultiFactorID,
+				PhoneNumber:         factor.PhoneInfo,
+				Phone: &PhoneMultiFactorInfo{
+					PhoneNumber: factor.PhoneInfo,
+				},
+			})
+		} else if factor.TOTPInfo != nil {
+			enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
+				UID:                 factor.MFAEnrollmentID,
+				DisplayName:         factor.DisplayName,
+				EnrollmentTimestamp: enrollmentTimestamp,
+				FactorID:            totpMultiFactorID,
+				TOTP:                &TOTPMultiFactorInfo{},
+			})
+		} else {
 			return nil, fmt.Errorf("unsupported multi-factor auth response: %#v", factor)
 		}
-
-		enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
-			UID:                 factor.MFAEnrollmentID,
-			DisplayName:         factor.DisplayName,
-			EnrollmentTimestamp: enrollmentTimestamp,
-			FactorID:            phoneMultiFactorID,
-			PhoneNumber:         factor.PhoneInfo,
-		})
 	}
 
 	return &ExportedUserRecord{


Test patch:
diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go
index 5143a2f..ffa4865 100644
--- a/auth/user_mgt_test.go
+++ b/auth/user_mgt_test.go
@@ -69,11 +69,21 @@ var testUser = &UserRecord{
 	MultiFactor: &MultiFactorSettings{
 		EnrolledFactors: []*MultiFactorInfo{
 			{
-				UID:                 "0aaded3f-5e73-461d-aef9-37b48e3769be",
+				UID:                 "enrolledPhoneFactor",
 				FactorID:            "phone",
 				EnrollmentTimestamp: 1614776780000,
-				PhoneNumber:         "+1234567890",
-				DisplayName:         "My MFA Phone",
+				Phone: &PhoneMultiFactorInfo{
+					PhoneNumber: "+1234567890",
+				},
+				PhoneNumber: "+1234567890",
+				DisplayName: "My MFA Phone",
+			},
+			{
+				UID:                 "enrolledTOTPFactor",
+				FactorID:            "totp",
+				EnrollmentTimestamp: 1614776780000,
+				TOTP:                &TOTPMultiFactorInfo{},
+				DisplayName:         "My MFA TOTP",
 			},
 		},
 	},
@@ -646,8 +656,10 @@ func TestInvalidCreateUser(t *testing.T) {
 			(&UserToCreate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						UID:         "EnrollmentID",
-						PhoneNumber: "+11234567890",
+						UID: "EnrollmentID",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "+11234567890",
+						},
 						DisplayName: "Spouse's phone number",
 						FactorID:    "phone",
 					},
@@ -658,7 +670,9 @@ func TestInvalidCreateUser(t *testing.T) {
 			(&UserToCreate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						PhoneNumber: "invalid",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "invalid",
+						},
 						DisplayName: "Spouse's phone number",
 						FactorID:    "phone",
 					},
@@ -669,7 +683,9 @@ func TestInvalidCreateUser(t *testing.T) {
 			(&UserToCreate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						PhoneNumber:         "+11234567890",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "+11234567890",
+						},
 						DisplayName:         "Spouse's phone number",
 						FactorID:            "phone",
 						EnrollmentTimestamp: time.Now().UTC().Unix(),
@@ -681,7 +697,9 @@ func TestInvalidCreateUser(t *testing.T) {
 			(&UserToCreate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						PhoneNumber: "+11234567890",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "+11234567890",
+						},
 						DisplayName: "Spouse's phone number",
 						FactorID:    "",
 					},
@@ -692,8 +710,10 @@ func TestInvalidCreateUser(t *testing.T) {
 			(&UserToCreate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						PhoneNumber: "+11234567890",
-						FactorID:    "phone",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "+11234567890",
+						},
+						FactorID: "phone",
 					},
 				},
 			}),
@@ -772,9 +792,16 @@ var createUserCases = []struct {
 	}, {
 		(&UserToCreate{}).MFASettings(MultiFactorSettings{
 			EnrolledFactors: []*MultiFactorInfo{
+				{
+					Phone: &PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
+					DisplayName: "Phone Number active",
+					FactorID:    "phone",
+				},
 				{
 					PhoneNumber: "+11234567890",
-					DisplayName: "Spouse's phone number",
+					DisplayName: "Phone Number deprecated",
 					FactorID:    "phone",
 				},
 			},
@@ -782,7 +809,11 @@ var createUserCases = []struct {
 		map[string]interface{}{"mfaInfo": []*multiFactorInfoResponse{
 			{
 				PhoneInfo:   "+11234567890",
-				DisplayName: "Spouse's phone number",
+				DisplayName: "Phone Number active",
+			},
+			{
+				PhoneInfo:   "+11234567890",
+				DisplayName: "Phone Number deprecated",
 			},
 		},
 		},
@@ -790,12 +821,16 @@ var createUserCases = []struct {
 		(&UserToCreate{}).MFASettings(MultiFactorSettings{
 			EnrolledFactors: []*MultiFactorInfo{
 				{
-					PhoneNumber: "+11234567890",
+					Phone: &PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
 					DisplayName: "number1",
 					FactorID:    "phone",
 				},
 				{
-					PhoneNumber: "+11234567890",
+					Phone: &PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
 					DisplayName: "number2",
 					FactorID:    "phone",
 				},
@@ -875,9 +910,11 @@ func TestInvalidUpdateUser(t *testing.T) {
 			(&UserToUpdate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						UID:         "enrolledSecondFactor1",
-						PhoneNumber: "+11234567890",
-						FactorID:    "phone",
+						UID: "enrolledSecondFactor1",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "+11234567890",
+						},
+						FactorID: "phone",
 					},
 				},
 			}),
@@ -886,8 +923,10 @@ func TestInvalidUpdateUser(t *testing.T) {
 			(&UserToUpdate{}).MFASettings(MultiFactorSettings{
 				EnrolledFactors: []*MultiFactorInfo{
 					{
-						UID:         "enrolledSecondFactor1",
-						PhoneNumber: "invalid",
+						UID: "enrolledSecondFactor1",
+						Phone: &PhoneMultiFactorInfo{
+							PhoneNumber: "invalid",
+						},
 						DisplayName: "Spouse's phone number",
 						FactorID:    "phone",
 					},
@@ -1038,17 +1077,25 @@ var updateUserCases = []struct {
 		(&UserToUpdate{}).MFASettings(MultiFactorSettings{
 			EnrolledFactors: []*MultiFactorInfo{
 				{
-					UID:                 "enrolledSecondFactor1",
-					PhoneNumber:         "+11234567890",
+					UID: "enrolledSecondFactor1",
+					Phone: &PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
 					DisplayName:         "Spouse's phone number",
 					FactorID:            "phone",
 					EnrollmentTimestamp: time.Now().Unix(),
 				}, {
-					UID:         "enrolledSecondFactor2",
+					UID: "enrolledSecondFactor2",
+					Phone: &PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
 					PhoneNumber: "+11234567890",
 					DisplayName: "Spouse's phone number",
 					FactorID:    "phone",
 				}, {
+					Phone: &PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
 					PhoneNumber: "+11234567890",
 					DisplayName: "Spouse's phone number",
 					FactorID:    "phone",
@@ -1883,10 +1930,16 @@ func TestMakeExportedUser(t *testing.T) {
 		MFAInfo: []*multiFactorInfoResponse{
 			{
 				PhoneInfo:       "+1234567890",
-				MFAEnrollmentID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
+				MFAEnrollmentID: "enrolledPhoneFactor",
 				DisplayName:     "My MFA Phone",
 				EnrolledAt:      "2021-03-03T13:06:20.542896Z",
 			},
+			{
+				TOTPInfo:        &TOTPInfo{},
+				MFAEnrollmentID: "enrolledTOTPFactor",
+				DisplayName:     "My MFA TOTP",
+				EnrolledAt:      "2021-03-03T13:06:20.542896Z",
+			},
 		},
 	}
 
diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go
index c1301f6..1c37cd0 100644
--- a/integration/auth/user_mgt_test.go
+++ b/integration/auth/user_mgt_test.go
@@ -435,7 +435,14 @@ func TestCreateUserMFA(t *testing.T) {
 		EnrolledFactors: []*auth.MultiFactorInfo{
 			{
 				PhoneNumber: "+11234567890",
-				DisplayName: "Spouse's phone number",
+				DisplayName: "Phone Number deprecated",
+				FactorID:    "phone",
+			},
+			{
+				Phone: &auth.PhoneMultiFactorInfo{
+					PhoneNumber: "+19876543210",
+				},
+				DisplayName: "Phone Number active",
 				FactorID:    "phone",
 			},
 		},
@@ -445,14 +452,27 @@ func TestCreateUserMFA(t *testing.T) {
 		t.Fatalf("CreateUser() = %v; want = nil", err)
 	}
 	defer deleteUser(user.UID)
-	var factor []*auth.MultiFactorInfo = []*auth.MultiFactorInfo{
+	var factors []*auth.MultiFactorInfo = []*auth.MultiFactorInfo{
 		{
-			UID:                 user.MultiFactor.EnrolledFactors[0].UID,
-			DisplayName:         "Spouse's phone number",
-			FactorID:            "phone",
+			UID:         user.MultiFactor.EnrolledFactors[0].UID,
+			DisplayName: "Phone Number deprecated",
+			FactorID:    "phone",
+			Phone: &auth.PhoneMultiFactorInfo{
+				PhoneNumber: "+11234567890",
+			},
 			PhoneNumber:         "+11234567890",
 			EnrollmentTimestamp: user.MultiFactor.EnrolledFactors[0].EnrollmentTimestamp,
 		},
+		{
+			UID:         user.MultiFactor.EnrolledFactors[1].UID,
+			DisplayName: "Phone Number active",
+			FactorID:    "phone",
+			Phone: &auth.PhoneMultiFactorInfo{
+				PhoneNumber: "+19876543210",
+			},
+			PhoneNumber:         "+19876543210",
+			EnrollmentTimestamp: user.MultiFactor.EnrolledFactors[1].EnrollmentTimestamp,
+		},
 	}
 	want := auth.UserRecord{
 		EmailVerified: true,
@@ -466,7 +486,7 @@ func TestCreateUserMFA(t *testing.T) {
 		},
 		TokensValidAfterMillis: user.TokensValidAfterMillis,
 		MultiFactor: &auth.MultiFactorSettings{
-			EnrolledFactors: factor,
+			EnrolledFactors: factors,
 		},
 	}
 	if !reflect.DeepEqual(*user, want) {
@@ -710,6 +730,113 @@ func TestUpdateUser(t *testing.T) {
 	})
 }
 
+func TestUpdateUserMFA(t *testing.T) {
+	// Creates a new user for testing purposes. The user's uid will be
+	// '$name_$tenRandomChars' and email will be
+	// '$name_$tenRandomChars@example.com'.
+	createTestUserWithMFA := func(name string) *auth.UserRecord {
+		// TODO(rsgowman: This function could usefully be employed throughout
+		// this file.
+		tenRandomChars := generateRandomAlphaNumericString(10)
+		userRecord, err := client.CreateUser(context.Background(),
+			(&auth.UserToCreate{}).
+				Email(name+"_"+tenRandomChars+"@example.com").
+				EmailVerified(true).
+				MFASettings(auth.MultiFactorSettings{
+					EnrolledFactors: []*auth.MultiFactorInfo{
+						{
+							Phone: &auth.PhoneMultiFactorInfo{
+								PhoneNumber: "+11234567890",
+							},
+							DisplayName: "Phone Number active",
+							FactorID:    "phone",
+						},
+						{
+							PhoneNumber: "+19876543210",
+							DisplayName: "Phone Number deprecated",
+							FactorID:    "phone",
+						},
+					},
+				}),
+		)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return userRecord
+	}
+	// Create a test user with MFA settings for testing
+	user := createTestUserWithMFA("UpdateUserMFA")
+	defer deleteUser(user.UID)
+
+	// Define the updated MFA factors
+	updatedFactors := []*auth.MultiFactorInfo{
+		{
+			DisplayName: "Phone Number active updated",
+			FactorID:    "phone",
+			Phone: &auth.PhoneMultiFactorInfo{
+				PhoneNumber: "+11234567890",
+			},
+		},
+		{
+			DisplayName: "Phone Number deprecated updated",
+			FactorID:    "phone",
+			PhoneNumber: "+19876543210",
+		},
+	}
+
+	// Update the MFA settings
+	params := (&auth.UserToUpdate{}).MFASettings(auth.MultiFactorSettings{
+		EnrolledFactors: updatedFactors,
+	})
+
+	updatedUser, err := client.UpdateUser(context.Background(), user.UID, params)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want := auth.UserRecord{
+		EmailVerified: true,
+		UserInfo: &auth.UserInfo{
+			Email:      updatedUser.Email,
+			UID:        updatedUser.UID,
+			ProviderID: "firebase",
+		},
+		UserMetadata: &auth.UserMetadata{
+			CreationTimestamp: updatedUser.UserMetadata.CreationTimestamp,
+		},
+		TokensValidAfterMillis: updatedUser.TokensValidAfterMillis,
+		MultiFactor: &auth.MultiFactorSettings{
+			EnrolledFactors: []*auth.MultiFactorInfo{
+				{
+					UID: updatedUser.MultiFactor.EnrolledFactors[0].UID,
+					Phone: &auth.PhoneMultiFactorInfo{
+						PhoneNumber: "+11234567890",
+					},
+					PhoneNumber:         "+11234567890",
+					DisplayName:         "Phone Number active updated",
+					FactorID:            "phone",
+					EnrollmentTimestamp: updatedUser.MultiFactor.EnrolledFactors[0].EnrollmentTimestamp,
+				},
+				{
+					UID: updatedUser.MultiFactor.EnrolledFactors[1].UID,
+					Phone: &auth.PhoneMultiFactorInfo{
+						PhoneNumber: "+19876543210",
+					},
+					PhoneNumber:         "+19876543210",
+					DisplayName:         "Phone Number deprecated updated",
+					FactorID:            "phone",
+					EnrollmentTimestamp: updatedUser.MultiFactor.EnrolledFactors[1].EnrollmentTimestamp,
+				},
+			},
+		},
+	}
+
+	// Compare the updated user with the expected user record
+	if !reflect.DeepEqual(*updatedUser, want) {
+		t.Errorf("UpdateUser() = %#v; want = %#v", *updatedUser, want)
+	}
+}
+
 func TestDisableUser(t *testing.T) {
 	user := newUserWithParams(t)
 	defer deleteUser(user.UID)
diff --git a/testdata/get_user.json b/testdata/get_user.json
index a2cac48..0bf86f9 100644
--- a/testdata/get_user.json
+++ b/testdata/get_user.json
@@ -35,9 +35,15 @@
       "mfaInfo": [
         {
           "phoneInfo": "+1234567890",
-          "mfaEnrollmentId": "0aaded3f-5e73-461d-aef9-37b48e3769be",
+          "mfaEnrollmentId": "enrolledPhoneFactor",
           "displayName": "My MFA Phone",
           "enrolledAt": "2021-03-03T13:06:20.542896Z"
+        },
+        {
+          "totpInfo": {},
+          "mfaEnrollmentId": "enrolledTOTPFactor",
+          "displayName": "My MFA TOTP",
+          "enrolledAt": "2021-03-03T13:06:20.542896Z"
         }
       ]
     }
diff --git a/testdata/list_users.json b/testdata/list_users.json
index bf94ff4..2b63068 100644
--- a/testdata/list_users.json
+++ b/testdata/list_users.json
@@ -33,12 +33,18 @@
             "customAttributes": "{\"admin\": true, \"package\": \"gold\"}",
             "tenantId": "testTenant",
             "mfaInfo": [
-              {
-                "phoneInfo": "+1234567890",
-                "mfaEnrollmentId": "0aaded3f-5e73-461d-aef9-37b48e3769be",
-                "displayName": "My MFA Phone",
-                "enrolledAt": "2021-03-03T13:06:20.542896Z"
-              }
+                {
+                    "phoneInfo": "+1234567890",
+                    "mfaEnrollmentId": "enrolledPhoneFactor",
+                    "displayName": "My MFA Phone",
+                    "enrolledAt": "2021-03-03T13:06:20.542896Z"
+                },
+                {
+                    "totpInfo": {},
+                    "mfaEnrollmentId": "enrolledTOTPFactor",
+                    "displayName": "My MFA TOTP",
+                    "enrolledAt": "2021-03-03T13:06:20.542896Z"
+                }
             ]
         },
         {
@@ -73,12 +79,18 @@
             "customAttributes": "{\"admin\": true, \"package\": \"gold\"}",
             "tenantId": "testTenant",
             "mfaInfo": [
-              {
-                "phoneInfo": "+1234567890",
-                "mfaEnrollmentId": "0aaded3f-5e73-461d-aef9-37b48e3769be",
-                "displayName": "My MFA Phone",
-                "enrolledAt": "2021-03-03T13:06:20.542896Z"
-              }
+                {
+                    "phoneInfo": "+1234567890",
+                    "mfaEnrollmentId": "enrolledPhoneFactor",
+                    "displayName": "My MFA Phone",
+                    "enrolledAt": "2021-03-03T13:06:20.542896Z"
+                },
+                {
+                    "totpInfo": {},
+                    "mfaEnrollmentId": "enrolledTOTPFactor",
+                    "displayName": "My MFA TOTP",
+                    "enrolledAt": "2021-03-03T13:06:20.542896Z"
+                }
             ]
         },
         {