Skip to content

Commit 4121c50

Browse files
Merge dev into master
2 parents 05378ef + 24bca17 commit 4121c50

File tree

9 files changed

+472
-39
lines changed

9 files changed

+472
-39
lines changed

auth/tenant_mgt_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func TestTenantListUsers(t *testing.T) {
164164
want := []*ExportedUserRecord{
165165
{UserRecord: testUser, PasswordHash: "passwordhash1", PasswordSalt: "salt1"},
166166
{UserRecord: testUser, PasswordHash: "passwordhash2", PasswordSalt: "salt2"},
167-
{UserRecord: testUser, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
167+
{UserRecord: testUserWithoutMFA, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
168168
}
169169

170170
testIterator := func(iter *UserIterator, token string, req string) {

auth/user_mgt.go

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ type UserInfo struct {
5757
UID string `json:"rawId,omitempty"`
5858
}
5959

60+
// multiFactorInfoResponse describes the `mfaInfo` of the user record API response
61+
type multiFactorInfoResponse struct {
62+
MFAEnrollmentID string `json:"mfaEnrollmentId,omitempty"`
63+
DisplayName string `json:"displayName,omitempty"`
64+
PhoneInfo string `json:"phoneInfo,omitempty"`
65+
EnrolledAt string `json:"enrolledAt,omitempty"`
66+
}
67+
68+
// MultiFactorInfo describes a user enrolled second phone factor.
69+
type MultiFactorInfo struct {
70+
UID string
71+
DisplayName string
72+
EnrollmentTimestamp int64
73+
FactorID string
74+
PhoneNumber string
75+
}
76+
77+
// MultiFactorSettings describes the multi-factor related user settings.
78+
type MultiFactorSettings struct {
79+
EnrolledFactors []*MultiFactorInfo
80+
}
81+
6082
// UserMetadata contains additional metadata associated with a user account.
6183
// Timestamps are in milliseconds since epoch.
6284
type UserMetadata struct {
@@ -77,6 +99,7 @@ type UserRecord struct {
7799
TokensValidAfterMillis int64 // milliseconds since epoch.
78100
UserMetadata *UserMetadata
79101
TenantID string
102+
MultiFactor *MultiFactorSettings
80103
}
81104

82105
// UserToCreate is the parameter struct for the CreateUser function.
@@ -892,23 +915,24 @@ func (c *baseClient) GetUsers(
892915
}
893916

894917
type userQueryResponse struct {
895-
UID string `json:"localId,omitempty"`
896-
DisplayName string `json:"displayName,omitempty"`
897-
Email string `json:"email,omitempty"`
898-
PhoneNumber string `json:"phoneNumber,omitempty"`
899-
PhotoURL string `json:"photoUrl,omitempty"`
900-
CreationTimestamp int64 `json:"createdAt,string,omitempty"`
901-
LastLogInTimestamp int64 `json:"lastLoginAt,string,omitempty"`
902-
LastRefreshAt string `json:"lastRefreshAt,omitempty"`
903-
ProviderID string `json:"providerId,omitempty"`
904-
CustomAttributes string `json:"customAttributes,omitempty"`
905-
Disabled bool `json:"disabled,omitempty"`
906-
EmailVerified bool `json:"emailVerified,omitempty"`
907-
ProviderUserInfo []*UserInfo `json:"providerUserInfo,omitempty"`
908-
PasswordHash string `json:"passwordHash,omitempty"`
909-
PasswordSalt string `json:"salt,omitempty"`
910-
TenantID string `json:"tenantId,omitempty"`
911-
ValidSinceSeconds int64 `json:"validSince,string,omitempty"`
918+
UID string `json:"localId,omitempty"`
919+
DisplayName string `json:"displayName,omitempty"`
920+
Email string `json:"email,omitempty"`
921+
PhoneNumber string `json:"phoneNumber,omitempty"`
922+
PhotoURL string `json:"photoUrl,omitempty"`
923+
CreationTimestamp int64 `json:"createdAt,string,omitempty"`
924+
LastLogInTimestamp int64 `json:"lastLoginAt,string,omitempty"`
925+
LastRefreshAt string `json:"lastRefreshAt,omitempty"`
926+
ProviderID string `json:"providerId,omitempty"`
927+
CustomAttributes string `json:"customAttributes,omitempty"`
928+
Disabled bool `json:"disabled,omitempty"`
929+
EmailVerified bool `json:"emailVerified,omitempty"`
930+
ProviderUserInfo []*UserInfo `json:"providerUserInfo,omitempty"`
931+
PasswordHash string `json:"passwordHash,omitempty"`
932+
PasswordSalt string `json:"salt,omitempty"`
933+
TenantID string `json:"tenantId,omitempty"`
934+
ValidSinceSeconds int64 `json:"validSince,string,omitempty"`
935+
MFAInfo []*multiFactorInfoResponse `json:"mfaInfo,omitempty"`
912936
}
913937

914938
func (r *userQueryResponse) makeUserRecord() (*UserRecord, error) {
@@ -948,6 +972,32 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
948972
lastRefreshTimestamp = t.Unix() * 1000
949973
}
950974

975+
// Map the MFA info to a slice of enrolled factors. Currently there is only
976+
// support for PhoneMultiFactorInfo.
977+
var enrolledFactors []*MultiFactorInfo
978+
for _, factor := range r.MFAInfo {
979+
var enrollmentTimestamp int64
980+
if factor.EnrolledAt != "" {
981+
t, err := time.Parse(time.RFC3339, factor.EnrolledAt)
982+
if err != nil {
983+
return nil, err
984+
}
985+
enrollmentTimestamp = t.Unix() * 1000
986+
}
987+
988+
if factor.PhoneInfo == "" {
989+
return nil, fmt.Errorf("unsupported multi-factor auth response: %#v", factor)
990+
}
991+
992+
enrolledFactors = append(enrolledFactors, &MultiFactorInfo{
993+
UID: factor.MFAEnrollmentID,
994+
DisplayName: factor.DisplayName,
995+
EnrollmentTimestamp: enrollmentTimestamp,
996+
FactorID: "phone",
997+
PhoneNumber: factor.PhoneInfo,
998+
})
999+
}
1000+
9511001
return &ExportedUserRecord{
9521002
UserRecord: &UserRecord{
9531003
UserInfo: &UserInfo{
@@ -969,6 +1019,9 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
9691019
CreationTimestamp: r.CreationTimestamp,
9701020
LastRefreshTimestamp: lastRefreshTimestamp,
9711021
},
1022+
MultiFactor: &MultiFactorSettings{
1023+
EnrolledFactors: enrolledFactors,
1024+
},
9721025
},
9731026
PasswordHash: hash,
9741027
PasswordSalt: r.PasswordSalt,

auth/user_mgt_test.go

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ var testUser = &UserRecord{
4444
PhotoURL: "http://www.example.com/testuser/photo.png",
4545
ProviderID: defaultProviderID,
4646
},
47-
Disabled: false,
48-
47+
Disabled: false,
4948
EmailVerified: true,
5049
ProviderUserInfo: []*UserInfo{
5150
{
@@ -67,6 +66,51 @@ var testUser = &UserRecord{
6766
},
6867
CustomClaims: map[string]interface{}{"admin": true, "package": "gold"},
6968
TenantID: "testTenant",
69+
MultiFactor: &MultiFactorSettings{
70+
EnrolledFactors: []*MultiFactorInfo{
71+
{
72+
UID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
73+
FactorID: "phone",
74+
EnrollmentTimestamp: 1614776780000,
75+
PhoneNumber: "+1234567890",
76+
DisplayName: "My MFA Phone",
77+
},
78+
},
79+
},
80+
}
81+
82+
var testUserWithoutMFA = &UserRecord{
83+
UserInfo: &UserInfo{
84+
UID: "testusernomfa",
85+
Email: "testusernomfa@example.com",
86+
PhoneNumber: "+1234567890",
87+
DisplayName: "Test User Without MFA",
88+
PhotoURL: "http://www.example.com/testusernomfa/photo.png",
89+
ProviderID: defaultProviderID,
90+
},
91+
Disabled: false,
92+
EmailVerified: true,
93+
ProviderUserInfo: []*UserInfo{
94+
{
95+
ProviderID: "password",
96+
DisplayName: "Test User Without MFA",
97+
PhotoURL: "http://www.example.com/testusernomfa/photo.png",
98+
Email: "testusernomfa@example.com",
99+
UID: "testuid",
100+
}, {
101+
ProviderID: "phone",
102+
PhoneNumber: "+1234567890",
103+
UID: "testuid",
104+
},
105+
},
106+
TokensValidAfterMillis: 1494364393000,
107+
UserMetadata: &UserMetadata{
108+
CreationTimestamp: 1234567890000,
109+
LastLogInTimestamp: 1233211232000,
110+
},
111+
CustomClaims: map[string]interface{}{"admin": true, "package": "gold"},
112+
TenantID: "testTenant",
113+
MultiFactor: &MultiFactorSettings{},
70114
}
71115

72116
func TestGetUser(t *testing.T) {
@@ -501,7 +545,7 @@ func TestListUsers(t *testing.T) {
501545
want := []*ExportedUserRecord{
502546
{UserRecord: testUser, PasswordHash: "passwordhash1", PasswordSalt: "salt1"},
503547
{UserRecord: testUser, PasswordHash: "passwordhash2", PasswordSalt: "salt2"},
504-
{UserRecord: testUser, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
548+
{UserRecord: testUserWithoutMFA, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
505549
}
506550

507551
testIterator := func(iter *UserIterator, token string, req string) {
@@ -1596,6 +1640,14 @@ func TestMakeExportedUser(t *testing.T) {
15961640
PhoneNumber: "+1234567890",
15971641
UID: "testuid",
15981642
}},
1643+
MFAInfo: []*multiFactorInfoResponse{
1644+
{
1645+
PhoneInfo: "+1234567890",
1646+
MFAEnrollmentID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
1647+
DisplayName: "My MFA Phone",
1648+
EnrolledAt: "2021-03-03T13:06:20.542896Z",
1649+
},
1650+
},
15991651
}
16001652

16011653
want := &ExportedUserRecord{
@@ -1620,6 +1672,22 @@ func TestMakeExportedUser(t *testing.T) {
16201672
}
16211673
}
16221674

1675+
func TestUnsupportedAuthFactor(t *testing.T) {
1676+
queryResponse := &userQueryResponse{
1677+
UID: "uid1",
1678+
MFAInfo: []*multiFactorInfoResponse{
1679+
{
1680+
MFAEnrollmentID: "enrollementId",
1681+
},
1682+
},
1683+
}
1684+
1685+
exported, err := queryResponse.makeExportedUserRecord()
1686+
if exported != nil || err == nil {
1687+
t.Errorf("makeExportedUserRecord() = (%v, %v); want = (nil, error)", exported, err)
1688+
}
1689+
}
1690+
16231691
func TestExportedUserRecordShouldClearRedacted(t *testing.T) {
16241692
queryResponse := &userQueryResponse{
16251693
UID: "uid1",

firebase.go

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

4040
// Version of the Firebase Go Admin SDK.
41-
const Version = "4.4.0"
41+
const Version = "4.5.0"
4242

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

go.mod

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ module firebase.google.com/go/v4
33
go 1.11
44

55
require (
6-
cloud.google.com/go/firestore v1.1.1
7-
cloud.google.com/go/storage v1.0.0
8-
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
9-
google.golang.org/api v0.17.0
10-
google.golang.org/appengine v1.6.1
11-
google.golang.org/grpc v1.29.1 // indirect
6+
cloud.google.com/go/firestore v1.5.0
7+
cloud.google.com/go/storage v1.10.0
8+
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99
9+
google.golang.org/api v0.40.0
10+
google.golang.org/appengine v1.6.7
1211
)

0 commit comments

Comments
 (0)