Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion auth/tenant_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func TestTenantListUsers(t *testing.T) {
want := []*ExportedUserRecord{
{UserRecord: testUser, PasswordHash: "passwordhash1", PasswordSalt: "salt1"},
{UserRecord: testUser, PasswordHash: "passwordhash2", PasswordSalt: "salt2"},
{UserRecord: testUser, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
{UserRecord: testUserWithoutMFA, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
}

testIterator := func(iter *UserIterator, token string, req string) {
Expand Down
92 changes: 75 additions & 17 deletions auth/user_mgt.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@ type UserInfo struct {
UID string `json:"rawId,omitempty"`
}

// MultiFactorInfoResponse describes the `mfaInfo` of the user record API response
type MultiFactorInfoResponse struct {
MFAEnrollmentID string `json:"mfaEnrollmentId"`
DisplayName string `json:"displayName"`
PhoneInfo string `json:"phoneInfo"`
EnrolledAt string `json:"enrolledAt"`
}

// MultiFactorID represents the type of an enrolled factor, for now only Phone
// is available.
type MultiFactorID string

const (
// Phone represents an enrolled factor of type Phone / SMS
Phone MultiFactorID = "phone"
)

// PhoneMultiFactorInfo describes a user enrolled second phone factor.
type PhoneMultiFactorInfo struct {
UID string
DisplayName string
EnrollmentTimestamp int64
FactorID MultiFactorID
PhoneNumber string
}

// MultiFactorSettings describes the multi-factor related user settings.
type MultiFactorSettings struct {
EnrolledFactors []*PhoneMultiFactorInfo
}

// UserMetadata contains additional metadata associated with a user account.
// Timestamps are in milliseconds since epoch.
type UserMetadata struct {
Expand All @@ -76,6 +107,7 @@ type UserRecord struct {
TokensValidAfterMillis int64 // milliseconds since epoch.
UserMetadata *UserMetadata
TenantID string
MultiFactor *MultiFactorSettings
}

// UserToCreate is the parameter struct for the CreateUser function.
Expand Down Expand Up @@ -733,23 +765,24 @@ func (c *baseClient) GetUsers(
}

type userQueryResponse struct {
UID string `json:"localId,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PhotoURL string `json:"photoUrl,omitempty"`
CreationTimestamp int64 `json:"createdAt,string,omitempty"`
LastLogInTimestamp int64 `json:"lastLoginAt,string,omitempty"`
LastRefreshAt string `json:"lastRefreshAt,omitempty"`
ProviderID string `json:"providerId,omitempty"`
CustomAttributes string `json:"customAttributes,omitempty"`
Disabled bool `json:"disabled,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
ProviderUserInfo []*UserInfo `json:"providerUserInfo,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
PasswordSalt string `json:"salt,omitempty"`
TenantID string `json:"tenantId,omitempty"`
ValidSinceSeconds int64 `json:"validSince,string,omitempty"`
UID string `json:"localId,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Email string `json:"email,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PhotoURL string `json:"photoUrl,omitempty"`
CreationTimestamp int64 `json:"createdAt,string,omitempty"`
LastLogInTimestamp int64 `json:"lastLoginAt,string,omitempty"`
LastRefreshAt string `json:"lastRefreshAt,omitempty"`
ProviderID string `json:"providerId,omitempty"`
CustomAttributes string `json:"customAttributes,omitempty"`
Disabled bool `json:"disabled,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
ProviderUserInfo []*UserInfo `json:"providerUserInfo,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
PasswordSalt string `json:"salt,omitempty"`
TenantID string `json:"tenantId,omitempty"`
ValidSinceSeconds int64 `json:"validSince,string,omitempty"`
MFAInfo []*MultiFactorInfoResponse `json:"mfaInfo,omitempty"`
}

func (r *userQueryResponse) makeUserRecord() (*UserRecord, error) {
Expand Down Expand Up @@ -789,6 +822,28 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
lastRefreshTimestamp = t.Unix() * 1000
}

// Map the MFA info to a slice of enrolled factors. Currently there is only
// support for PhoneMultiFactorInfo.
var enrolledFactors []*PhoneMultiFactorInfo
for _, factor := range r.MFAInfo {
var enrollmentTimestamp int64
if factor.EnrolledAt != "" {
t, err := time.Parse(time.RFC3339, factor.EnrolledAt)
if err != nil {
return nil, err
}
enrollmentTimestamp = t.Unix() * 1000
}

enrolledFactors = append(enrolledFactors, &PhoneMultiFactorInfo{
UID: factor.MFAEnrollmentID,
DisplayName: factor.DisplayName,
EnrollmentTimestamp: enrollmentTimestamp,
FactorID: Phone,
PhoneNumber: factor.PhoneInfo,
})
}

return &ExportedUserRecord{
UserRecord: &UserRecord{
UserInfo: &UserInfo{
Expand All @@ -810,6 +865,9 @@ func (r *userQueryResponse) makeExportedUserRecord() (*ExportedUserRecord, error
CreationTimestamp: r.CreationTimestamp,
LastRefreshTimestamp: lastRefreshTimestamp,
},
MultiFactor: &MultiFactorSettings{
EnrolledFactors: enrolledFactors,
},
},
PasswordHash: hash,
PasswordSalt: r.PasswordSalt,
Expand Down
59 changes: 58 additions & 1 deletion auth/user_mgt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,55 @@ var testUser = &UserRecord{
},
CustomClaims: map[string]interface{}{"admin": true, "package": "gold"},
TenantID: "testTenant",
MultiFactor: &MultiFactorSettings{
EnrolledFactors: []*PhoneMultiFactorInfo{
{
UID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
FactorID: Phone,
EnrollmentTimestamp: 1614776780000,
PhoneNumber: "+31612345678",
DisplayName: "My MFA Phone",
},
},
},
}

var emptyFactors []*PhoneMultiFactorInfo
var testUserWithoutMFA = &UserRecord{
UserInfo: &UserInfo{
UID: "testusernomfa",
Email: "[email protected]",
PhoneNumber: "+1234567890",
DisplayName: "Test User Without MFA",
PhotoURL: "http://www.example.com/testusernomfa/photo.png",
ProviderID: defaultProviderID,
},
Disabled: false,

EmailVerified: true,
ProviderUserInfo: []*UserInfo{
{
ProviderID: "password",
DisplayName: "Test User Without MFA",
PhotoURL: "http://www.example.com/testusernomfa/photo.png",
Email: "[email protected]",
UID: "testuid",
}, {
ProviderID: "phone",
PhoneNumber: "+1234567890",
UID: "testuid",
},
},
TokensValidAfterMillis: 1494364393000,
UserMetadata: &UserMetadata{
CreationTimestamp: 1234567890000,
LastLogInTimestamp: 1233211232000,
},
CustomClaims: map[string]interface{}{"admin": true, "package": "gold"},
TenantID: "testTenant",
MultiFactor: &MultiFactorSettings{
EnrolledFactors: emptyFactors,
},
}

func TestGetUser(t *testing.T) {
Expand Down Expand Up @@ -427,7 +476,7 @@ func TestListUsers(t *testing.T) {
want := []*ExportedUserRecord{
{UserRecord: testUser, PasswordHash: "passwordhash1", PasswordSalt: "salt1"},
{UserRecord: testUser, PasswordHash: "passwordhash2", PasswordSalt: "salt2"},
{UserRecord: testUser, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
{UserRecord: testUserWithoutMFA, PasswordHash: "passwordhash3", PasswordSalt: "salt3"},
}

testIterator := func(iter *UserIterator, token string, req string) {
Expand Down Expand Up @@ -1443,6 +1492,14 @@ func TestMakeExportedUser(t *testing.T) {
PhoneNumber: "+1234567890",
UID: "testuid",
}},
MFAInfo: []*MultiFactorInfoResponse{
{
PhoneInfo: "+31612345678",
MFAEnrollmentID: "0aaded3f-5e73-461d-aef9-37b48e3769be",
DisplayName: "My MFA Phone",
EnrolledAt: "2021-03-03T13:06:20.542896Z",
},
},
}

want := &ExportedUserRecord{
Expand Down
10 changes: 9 additions & 1 deletion testdata/get_user.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@
"createdAt": "1234567890000",
"lastLoginAt": "1233211232000",
"customAttributes": "{\"admin\": true, \"package\": \"gold\"}",
"tenantId": "testTenant"
"tenantId": "testTenant",
"mfaInfo": [
{
"phoneInfo": "+31612345678",
"mfaEnrollmentId": "0aaded3f-5e73-461d-aef9-37b48e3769be",
"displayName": "My MFA Phone",
"enrolledAt": "2021-03-03T13:06:20.542896Z"
}
]
}
]
}
36 changes: 26 additions & 10 deletions testdata/list_users.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@
"createdAt": "1234567890000",
"lastLoginAt": "1233211232000",
"customAttributes": "{\"admin\": true, \"package\": \"gold\"}",
"tenantId": "testTenant"
"tenantId": "testTenant",
"mfaInfo": [
{
"phoneInfo": "+31612345678",
"mfaEnrollmentId": "0aaded3f-5e73-461d-aef9-37b48e3769be",
"displayName": "My MFA Phone",
"enrolledAt": "2021-03-03T13:06:20.542896Z"
}
]
},
{
"localId": "testuser",
Expand Down Expand Up @@ -63,21 +71,29 @@
"createdAt": "1234567890000",
"lastLoginAt": "1233211232000",
"customAttributes": "{\"admin\": true, \"package\": \"gold\"}",
"tenantId": "testTenant"
"tenantId": "testTenant",
"mfaInfo": [
{
"phoneInfo": "+31612345678",
"mfaEnrollmentId": "0aaded3f-5e73-461d-aef9-37b48e3769be",
"displayName": "My MFA Phone",
"enrolledAt": "2021-03-03T13:06:20.542896Z"
}
]
},
{
"localId": "testuser",
"email": "testuser@example.com",
"localId": "testusernomfa",
"email": "testusernomfa@example.com",
"phoneNumber": "+1234567890",
"emailVerified": true,
"displayName": "Test User",
"displayName": "Test User Without MFA",
"providerUserInfo": [
{
"providerId": "password",
"displayName": "Test User",
"photoUrl": "http://www.example.com/testuser/photo.png",
"federatedId": "testuser@example.com",
"email": "testuser@example.com",
"displayName": "Test User Without MFA",
"photoUrl": "http://www.example.com/testusernomfa/photo.png",
"federatedId": "testusernomfa@example.com",
"email": "testusernomfa@example.com",
"rawId": "testuid"
},
{
Expand All @@ -86,7 +102,7 @@
"rawId": "testuid"
}
],
"photoUrl": "http://www.example.com/testuser/photo.png",
"photoUrl": "http://www.example.com/testusernomfa/photo.png",
"passwordHash": "passwordhash3",
"salt": "salt3",
"passwordUpdatedAt": 1.494364393E+12,
Expand Down