Skip to content

Commit b04387e

Browse files
Merge dev into master
2 parents 37c7936 + a7e9d97 commit b04387e

File tree

10 files changed

+456
-81
lines changed

10 files changed

+456
-81
lines changed

auth/user_mgt.go

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const (
4242
createUserMethod = "createUser"
4343
updateUserMethod = "updateUser"
4444
phoneMultiFactorID = "phone"
45+
totpMultiFactorID = "totp"
4546
)
4647

4748
// 'REDACTED', encoded as a base64 string.
@@ -62,24 +63,37 @@ type UserInfo struct {
6263

6364
// multiFactorInfoResponse describes the `mfaInfo` of the user record API response
6465
type multiFactorInfoResponse struct {
65-
MFAEnrollmentID string `json:"mfaEnrollmentId,omitempty"`
66-
DisplayName string `json:"displayName,omitempty"`
67-
PhoneInfo string `json:"phoneInfo,omitempty"`
68-
EnrolledAt string `json:"enrolledAt,omitempty"`
66+
MFAEnrollmentID string `json:"mfaEnrollmentId,omitempty"`
67+
DisplayName string `json:"displayName,omitempty"`
68+
PhoneInfo string `json:"phoneInfo,omitempty"`
69+
TOTPInfo *TOTPInfo `json:"totpInfo,omitempty"`
70+
EnrolledAt string `json:"enrolledAt,omitempty"`
6971
}
7072

73+
// TOTPInfo describes a user enrolled second TOTP factor.
74+
type TOTPInfo struct{}
75+
76+
// PhoneMultiFactorInfo describes a user enrolled in SMS second factor.
77+
type PhoneMultiFactorInfo struct {
78+
PhoneNumber string
79+
}
80+
81+
// TOTPMultiFactorInfo describes a user enrolled in TOTP second factor.
82+
type TOTPMultiFactorInfo struct{}
83+
7184
type multiFactorEnrollments struct {
7285
Enrollments []*multiFactorInfoResponse `json:"enrollments"`
7386
}
7487

7588
// MultiFactorInfo describes a user enrolled second phone factor.
76-
// TODO : convert PhoneNumber to PhoneMultiFactorInfo struct
7789
type MultiFactorInfo struct {
7890
UID string
7991
DisplayName string
8092
EnrollmentTimestamp int64
8193
FactorID string
82-
PhoneNumber string
94+
PhoneNumber string // Deprecated: Use PhoneMultiFactorInfo instead
95+
Phone *PhoneMultiFactorInfo
96+
TOTP *TOTPMultiFactorInfo
8397
}
8498

8599
// MultiFactorSettings describes the multi-factor related user settings.
@@ -177,12 +191,17 @@ func convertMultiFactorInfoToServerFormat(mfaInfo MultiFactorInfo) (multiFactorI
177191
if mfaInfo.UID != "" {
178192
authFactorInfo.MFAEnrollmentID = mfaInfo.UID
179193
}
180-
if mfaInfo.FactorID == phoneMultiFactorID {
181-
authFactorInfo.PhoneInfo = mfaInfo.PhoneNumber
182-
return authFactorInfo, nil
194+
195+
switch mfaInfo.FactorID {
196+
case phoneMultiFactorID:
197+
authFactorInfo.PhoneInfo = mfaInfo.Phone.PhoneNumber
198+
case totpMultiFactorID:
199+
authFactorInfo.TOTPInfo = (*TOTPInfo)(mfaInfo.TOTP)
200+
default:
201+
out, _ := json.Marshal(mfaInfo)
202+
return multiFactorInfoResponse{}, fmt.Errorf("unsupported second factor %s provided", string(out))
183203
}
184-
out, _ := json.Marshal(mfaInfo)
185-
return multiFactorInfoResponse{}, fmt.Errorf("unsupported second factor %s provided", string(out))
204+
return authFactorInfo, nil
186205
}
187206

188207
func (u *UserToCreate) validatedRequest() (map[string]interface{}, error) {
@@ -338,8 +357,7 @@ func (u *UserToUpdate) validatedRequest() (map[string]interface{}, error) {
338357
if err != nil {
339358
return nil, err
340359
}
341-
342-
// https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/update
360+
// Request body ref: https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/update
343361
req["mfa"] = multiFactorEnrollments{mfaInfo}
344362
} else {
345363
req[k] = v
@@ -679,8 +697,24 @@ func validateAndFormatMfaSettings(mfaSettings MultiFactorSettings, methodType st
679697
return nil, fmt.Errorf("the second factor \"displayName\" for \"%s\" must be a valid non-empty string", multiFactorInfo.DisplayName)
680698
}
681699
if multiFactorInfo.FactorID == phoneMultiFactorID {
682-
if err := validatePhone(multiFactorInfo.PhoneNumber); err != nil {
683-
return nil, fmt.Errorf("the second factor \"phoneNumber\" for \"%s\" must be a non-empty E.164 standard compliant identifier string", multiFactorInfo.PhoneNumber)
700+
if multiFactorInfo.Phone != nil {
701+
// If PhoneMultiFactorInfo is provided, validate its PhoneNumber field
702+
if err := validatePhone(multiFactorInfo.Phone.PhoneNumber); err != nil {
703+
return nil, fmt.Errorf("the second factor \"phoneNumber\" for \"%s\" must be a non-empty E.164 standard compliant identifier string", multiFactorInfo.Phone.PhoneNumber)
704+
}
705+
// No need for the else here since we are returning from the function
706+
} else if multiFactorInfo.PhoneNumber != "" {
707+
// PhoneMultiFactorInfo is nil, check the deprecated PhoneNumber field
708+
if err := validatePhone(multiFactorInfo.PhoneNumber); err != nil {
709+
return nil, fmt.Errorf("the second factor \"phoneNumber\" for \"%s\" must be a non-empty E.164 standard compliant identifier string", multiFactorInfo.PhoneNumber)
710+
}
711+
// The PhoneNumber field is deprecated, set it in PhoneMultiFactorInfo and inform about the deprecation.
712+
multiFactorInfo.Phone = &PhoneMultiFactorInfo{
713+
PhoneNumber: multiFactorInfo.PhoneNumber,
714+
}
715+
} else {
716+
// Both PhoneMultiFactorInfo and deprecated PhoneNumber are missing.
717+
return nil, fmt.Errorf("\"PhoneMultiFactorInfo\" must be defined")
684718
}
685719
}
686720
obj, err := convertMultiFactorInfoToServerFormat(*multiFactorInfo)
@@ -1079,17 +1113,28 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
10791113
enrollmentTimestamp = t.Unix() * 1000
10801114
}
10811115

1082-
if factor.PhoneInfo == "" {
1116+
if factor.PhoneInfo != "" {
1117+
enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
1118+
UID: factor.MFAEnrollmentID,
1119+
DisplayName: factor.DisplayName,
1120+
EnrollmentTimestamp: enrollmentTimestamp,
1121+
FactorID: phoneMultiFactorID,
1122+
PhoneNumber: factor.PhoneInfo,
1123+
Phone: &PhoneMultiFactorInfo{
1124+
PhoneNumber: factor.PhoneInfo,
1125+
},
1126+
})
1127+
} else if factor.TOTPInfo != nil {
1128+
enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
1129+
UID: factor.MFAEnrollmentID,
1130+
DisplayName: factor.DisplayName,
1131+
EnrollmentTimestamp: enrollmentTimestamp,
1132+
FactorID: totpMultiFactorID,
1133+
TOTP: &TOTPMultiFactorInfo{},
1134+
})
1135+
} else {
10831136
return nil, fmt.Errorf("unsupported multi-factor auth response: %#v", factor)
10841137
}
1085-
1086-
enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
1087-
UID: factor.MFAEnrollmentID,
1088-
DisplayName: factor.DisplayName,
1089-
EnrollmentTimestamp: enrollmentTimestamp,
1090-
FactorID: phoneMultiFactorID,
1091-
PhoneNumber: factor.PhoneInfo,
1092-
})
10931138
}
10941139

10951140
return &ExportedUserRecord{

auth/user_mgt_test.go

Lines changed: 76 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,21 @@ var testUser = &UserRecord{
6969
MultiFactor: &MultiFactorSettings{
7070
EnrolledFactors: []*MultiFactorInfo{
7171
{
72-
UID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
72+
UID: "enrolledPhoneFactor",
7373
FactorID: "phone",
7474
EnrollmentTimestamp: 1614776780000,
75-
PhoneNumber: "+1234567890",
76-
DisplayName: "My MFA Phone",
75+
Phone: &PhoneMultiFactorInfo{
76+
PhoneNumber: "+1234567890",
77+
},
78+
PhoneNumber: "+1234567890",
79+
DisplayName: "My MFA Phone",
80+
},
81+
{
82+
UID: "enrolledTOTPFactor",
83+
FactorID: "totp",
84+
EnrollmentTimestamp: 1614776780000,
85+
TOTP: &TOTPMultiFactorInfo{},
86+
DisplayName: "My MFA TOTP",
7787
},
7888
},
7989
},
@@ -646,8 +656,10 @@ func TestInvalidCreateUser(t *testing.T) {
646656
(&UserToCreate{}).MFASettings(MultiFactorSettings{
647657
EnrolledFactors: []*MultiFactorInfo{
648658
{
649-
UID: "EnrollmentID",
650-
PhoneNumber: "+11234567890",
659+
UID: "EnrollmentID",
660+
Phone: &PhoneMultiFactorInfo{
661+
PhoneNumber: "+11234567890",
662+
},
651663
DisplayName: "Spouse's phone number",
652664
FactorID: "phone",
653665
},
@@ -658,7 +670,9 @@ func TestInvalidCreateUser(t *testing.T) {
658670
(&UserToCreate{}).MFASettings(MultiFactorSettings{
659671
EnrolledFactors: []*MultiFactorInfo{
660672
{
661-
PhoneNumber: "invalid",
673+
Phone: &PhoneMultiFactorInfo{
674+
PhoneNumber: "invalid",
675+
},
662676
DisplayName: "Spouse's phone number",
663677
FactorID: "phone",
664678
},
@@ -669,7 +683,9 @@ func TestInvalidCreateUser(t *testing.T) {
669683
(&UserToCreate{}).MFASettings(MultiFactorSettings{
670684
EnrolledFactors: []*MultiFactorInfo{
671685
{
672-
PhoneNumber: "+11234567890",
686+
Phone: &PhoneMultiFactorInfo{
687+
PhoneNumber: "+11234567890",
688+
},
673689
DisplayName: "Spouse's phone number",
674690
FactorID: "phone",
675691
EnrollmentTimestamp: time.Now().UTC().Unix(),
@@ -681,7 +697,9 @@ func TestInvalidCreateUser(t *testing.T) {
681697
(&UserToCreate{}).MFASettings(MultiFactorSettings{
682698
EnrolledFactors: []*MultiFactorInfo{
683699
{
684-
PhoneNumber: "+11234567890",
700+
Phone: &PhoneMultiFactorInfo{
701+
PhoneNumber: "+11234567890",
702+
},
685703
DisplayName: "Spouse's phone number",
686704
FactorID: "",
687705
},
@@ -692,8 +710,10 @@ func TestInvalidCreateUser(t *testing.T) {
692710
(&UserToCreate{}).MFASettings(MultiFactorSettings{
693711
EnrolledFactors: []*MultiFactorInfo{
694712
{
695-
PhoneNumber: "+11234567890",
696-
FactorID: "phone",
713+
Phone: &PhoneMultiFactorInfo{
714+
PhoneNumber: "+11234567890",
715+
},
716+
FactorID: "phone",
697717
},
698718
},
699719
}),
@@ -772,30 +792,45 @@ var createUserCases = []struct {
772792
}, {
773793
(&UserToCreate{}).MFASettings(MultiFactorSettings{
774794
EnrolledFactors: []*MultiFactorInfo{
795+
{
796+
Phone: &PhoneMultiFactorInfo{
797+
PhoneNumber: "+11234567890",
798+
},
799+
DisplayName: "Phone Number active",
800+
FactorID: "phone",
801+
},
775802
{
776803
PhoneNumber: "+11234567890",
777-
DisplayName: "Spouse's phone number",
804+
DisplayName: "Phone Number deprecated",
778805
FactorID: "phone",
779806
},
780807
},
781808
}),
782809
map[string]interface{}{"mfaInfo": []*multiFactorInfoResponse{
783810
{
784811
PhoneInfo: "+11234567890",
785-
DisplayName: "Spouse's phone number",
812+
DisplayName: "Phone Number active",
813+
},
814+
{
815+
PhoneInfo: "+11234567890",
816+
DisplayName: "Phone Number deprecated",
786817
},
787818
},
788819
},
789820
}, {
790821
(&UserToCreate{}).MFASettings(MultiFactorSettings{
791822
EnrolledFactors: []*MultiFactorInfo{
792823
{
793-
PhoneNumber: "+11234567890",
824+
Phone: &PhoneMultiFactorInfo{
825+
PhoneNumber: "+11234567890",
826+
},
794827
DisplayName: "number1",
795828
FactorID: "phone",
796829
},
797830
{
798-
PhoneNumber: "+11234567890",
831+
Phone: &PhoneMultiFactorInfo{
832+
PhoneNumber: "+11234567890",
833+
},
799834
DisplayName: "number2",
800835
FactorID: "phone",
801836
},
@@ -875,9 +910,11 @@ func TestInvalidUpdateUser(t *testing.T) {
875910
(&UserToUpdate{}).MFASettings(MultiFactorSettings{
876911
EnrolledFactors: []*MultiFactorInfo{
877912
{
878-
UID: "enrolledSecondFactor1",
879-
PhoneNumber: "+11234567890",
880-
FactorID: "phone",
913+
UID: "enrolledSecondFactor1",
914+
Phone: &PhoneMultiFactorInfo{
915+
PhoneNumber: "+11234567890",
916+
},
917+
FactorID: "phone",
881918
},
882919
},
883920
}),
@@ -886,8 +923,10 @@ func TestInvalidUpdateUser(t *testing.T) {
886923
(&UserToUpdate{}).MFASettings(MultiFactorSettings{
887924
EnrolledFactors: []*MultiFactorInfo{
888925
{
889-
UID: "enrolledSecondFactor1",
890-
PhoneNumber: "invalid",
926+
UID: "enrolledSecondFactor1",
927+
Phone: &PhoneMultiFactorInfo{
928+
PhoneNumber: "invalid",
929+
},
891930
DisplayName: "Spouse's phone number",
892931
FactorID: "phone",
893932
},
@@ -1038,17 +1077,25 @@ var updateUserCases = []struct {
10381077
(&UserToUpdate{}).MFASettings(MultiFactorSettings{
10391078
EnrolledFactors: []*MultiFactorInfo{
10401079
{
1041-
UID: "enrolledSecondFactor1",
1042-
PhoneNumber: "+11234567890",
1080+
UID: "enrolledSecondFactor1",
1081+
Phone: &PhoneMultiFactorInfo{
1082+
PhoneNumber: "+11234567890",
1083+
},
10431084
DisplayName: "Spouse's phone number",
10441085
FactorID: "phone",
10451086
EnrollmentTimestamp: time.Now().Unix(),
10461087
}, {
1047-
UID: "enrolledSecondFactor2",
1088+
UID: "enrolledSecondFactor2",
1089+
Phone: &PhoneMultiFactorInfo{
1090+
PhoneNumber: "+11234567890",
1091+
},
10481092
PhoneNumber: "+11234567890",
10491093
DisplayName: "Spouse's phone number",
10501094
FactorID: "phone",
10511095
}, {
1096+
Phone: &PhoneMultiFactorInfo{
1097+
PhoneNumber: "+11234567890",
1098+
},
10521099
PhoneNumber: "+11234567890",
10531100
DisplayName: "Spouse's phone number",
10541101
FactorID: "phone",
@@ -1883,10 +1930,16 @@ func TestMakeExportedUser(t *testing.T) {
18831930
MFAInfo: []*multiFactorInfoResponse{
18841931
{
18851932
PhoneInfo: "+1234567890",
1886-
MFAEnrollmentID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
1933+
MFAEnrollmentID: "enrolledPhoneFactor",
18871934
DisplayName: "My MFA Phone",
18881935
EnrolledAt: "2021-03-03T13:06:20.542896Z",
18891936
},
1937+
{
1938+
TOTPInfo: &TOTPInfo{},
1939+
MFAEnrollmentID: "enrolledTOTPFactor",
1940+
DisplayName: "My MFA TOTP",
1941+
EnrolledAt: "2021-03-03T13:06:20.542896Z",
1942+
},
18901943
},
18911944
}
18921945

firebase.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import (
3939
var defaultAuthOverrides = make(map[string]interface{})
4040

4141
// Version of the Firebase Go Admin SDK.
42-
const Version = "4.12.1"
42+
const Version = "4.13.0"
4343

4444
// firebaseEnvName is the name of the environment variable with the Config.
4545
const firebaseEnvName = "FIREBASE_CONFIG"

0 commit comments

Comments
 (0)