Skip to content

Commit 9f28a3b

Browse files
authored
feat(auth): GetUserByProviderID() (#339)
1 parent 33bf95d commit 9f28a3b

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

auth/user_mgt.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,47 @@ func (c *baseClient) GetUserByPhoneNumber(ctx context.Context, phone string) (*U
609609
})
610610
}
611611

612+
// GetUserByProviderID gets the user data for the user corresponding to a given provider ID.
613+
//
614+
// See
615+
// [Retrieve user data](https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data)
616+
// for code samples and detailed documentation.
617+
//
618+
// `providerID` indicates the provider, such as 'google.com' for the Google provider.
619+
// `providerUID` is the user identifier for the given provider.
620+
func (c *baseClient) GetUserByProviderID(ctx context.Context, providerID string, providerUID string) (*UserRecord, error) {
621+
// Although we don't really advertise it, we want to also handle non-federated
622+
// IDPs with this call. So if we detect one of them, we'll reroute this
623+
// request appropriately.
624+
if providerID == "phone" {
625+
return c.GetUserByPhoneNumber(ctx, providerUID)
626+
} else if providerID == "email" {
627+
return c.GetUserByEmail(ctx, providerUID)
628+
}
629+
630+
if err := validateProvider(providerID, providerUID); err != nil {
631+
return nil, err
632+
}
633+
634+
getUsersResult, err := c.GetUsers(ctx, []UserIdentifier{&ProviderIdentifier{providerID, providerUID}})
635+
if err != nil {
636+
return nil, err
637+
}
638+
639+
if len(getUsersResult.Users) == 0 {
640+
return nil, &internal.FirebaseError{
641+
ErrorCode: internal.NotFound,
642+
String: fmt.Sprintf("cannot find user from providerID: { %s, %s }", providerID, providerUID),
643+
Response: nil,
644+
Ext: map[string]interface{}{
645+
authErrorCode: userNotFound,
646+
},
647+
}
648+
}
649+
650+
return getUsersResult.Users[0], nil
651+
}
652+
612653
type userQuery struct {
613654
field string
614655
value string

auth/user_mgt_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,22 +141,96 @@ func TestGetUserByPhoneNumber(t *testing.T) {
141141
}
142142
}
143143

144+
func TestGetUserByProviderIDNotFound(t *testing.T) {
145+
mockUsers := []byte(`{ "users": [] }`)
146+
s := echoServer(mockUsers, t)
147+
defer s.Close()
148+
149+
userRecord, err := s.Client.GetUserByProviderID(context.Background(), "google.com", "google_uid1")
150+
want := "cannot find user from providerID: { google.com, google_uid1 }"
151+
if userRecord != nil || err == nil || err.Error() != want || !IsUserNotFound(err) {
152+
t.Errorf("GetUserByProviderID() = (%v, %q); want = (nil, %q)", userRecord, err, want)
153+
}
154+
}
155+
156+
func TestGetUserByProviderId(t *testing.T) {
157+
cases := []struct {
158+
providerID string
159+
providerUID string
160+
want string
161+
}{
162+
{
163+
"google.com",
164+
"google_uid1",
165+
`{"federatedUserId":[{"providerId":"google.com","rawId":"google_uid1"}]}`,
166+
}, {
167+
"phone",
168+
"+15555550001",
169+
`{"phoneNumber":["+15555550001"]}`,
170+
}, {
171+
"email",
172+
"user@example.com",
173+
`{"email":["user@example.com"]}`,
174+
},
175+
}
176+
177+
// The resulting user isn't parsed, so it just needs to exist (even if it's empty).
178+
mockUsers := []byte(`{ "users": [{}] }`)
179+
s := echoServer(mockUsers, t)
180+
defer s.Close()
181+
182+
for _, tc := range cases {
183+
t.Run(tc.providerID+":"+tc.providerUID, func(t *testing.T) {
184+
185+
_, err := s.Client.GetUserByProviderID(context.Background(), tc.providerID, tc.providerUID)
186+
if err != nil {
187+
t.Fatalf("GetUserByProviderID() = %q", err)
188+
}
189+
190+
got := string(s.Rbody)
191+
if got != tc.want {
192+
t.Errorf("GetUserByProviderID() Req = %v; want = %v", got, tc.want)
193+
}
194+
195+
wantPath := "/projects/mock-project-id/accounts:lookup"
196+
if s.Req[0].RequestURI != wantPath {
197+
t.Errorf("GetUserByProviderID() URL = %q; want = %q", s.Req[0].RequestURI, wantPath)
198+
}
199+
})
200+
}
201+
}
202+
144203
func TestInvalidGetUser(t *testing.T) {
145204
client := &Client{
146205
baseClient: &baseClient{},
147206
}
207+
148208
user, err := client.GetUser(context.Background(), "")
149209
if user != nil || err == nil {
150210
t.Errorf("GetUser('') = (%v, %v); want = (nil, error)", user, err)
151211
}
212+
152213
user, err = client.GetUserByEmail(context.Background(), "")
153214
if user != nil || err == nil {
154215
t.Errorf("GetUserByEmail('') = (%v, %v); want = (nil, error)", user, err)
155216
}
217+
156218
user, err = client.GetUserByPhoneNumber(context.Background(), "")
157219
if user != nil || err == nil {
158220
t.Errorf("GetUserPhoneNumber('') = (%v, %v); want = (nil, error)", user, err)
159221
}
222+
223+
userRecord, err := client.GetUserByProviderID(context.Background(), "", "google_uid1")
224+
want := "providerID must be a non-empty string"
225+
if userRecord != nil || err == nil || err.Error() != want {
226+
t.Errorf("GetUserByProviderID() = (%v, %q); want = (nil, %q)", userRecord, err, want)
227+
}
228+
229+
userRecord, err = client.GetUserByProviderID(context.Background(), "google.com", "")
230+
want = "providerUID must be a non-empty string"
231+
if userRecord != nil || err == nil || err.Error() != want {
232+
t.Errorf("GetUserByProviderID() = (%v, %q); want = (nil, %q)", userRecord, err, want)
233+
}
160234
}
161235

162236
// Checks to see if the users list contain the given uids. Order is ignored.

integration/auth/user_mgt_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@ func TestGetUser(t *testing.T) {
8080
}
8181
}
8282

83+
func TestGetUserByProviderID(t *testing.T) {
84+
// TODO(rsgowman): Once we can link a provider id with a user, just do that
85+
// here instead of importing a new user.
86+
importUserUID := randomUID()
87+
providerUID := "google_" + importUserUID
88+
userToImport := (&auth.UserToImport{}).
89+
UID(importUserUID).
90+
Email(randomEmail(importUserUID)).
91+
PhoneNumber(randomPhoneNumber()).
92+
ProviderData([](*auth.UserProvider){
93+
&auth.UserProvider{
94+
ProviderID: "google.com",
95+
UID: providerUID,
96+
},
97+
})
98+
importUser(t, importUserUID, userToImport)
99+
defer deleteUser(importUserUID)
100+
101+
userRecord, err := client.GetUserByProviderID(context.Background(), "google.com", providerUID)
102+
if err != nil {
103+
t.Fatalf("GetUserByProviderID() = %q", err)
104+
}
105+
106+
if userRecord.UID != importUserUID {
107+
t.Errorf("GetUserByProviderID().UID = %v; want = %v", userRecord.UID, importUserUID)
108+
}
109+
}
110+
83111
func TestGetNonExistingUser(t *testing.T) {
84112
user, err := client.GetUser(context.Background(), "non.existing")
85113
if user != nil || !auth.IsUserNotFound(err) {
@@ -90,6 +118,16 @@ func TestGetNonExistingUser(t *testing.T) {
90118
if user != nil || !auth.IsUserNotFound(err) {
91119
t.Errorf("GetUserByEmail(non.existing) = (%v, %v); want = (nil, error)", user, err)
92120
}
121+
122+
user, err = client.GetUserByPhoneNumber(context.Background(), "+14044040404")
123+
if user != nil || !auth.IsUserNotFound(err) {
124+
t.Errorf("GetUser(non.existing) = (%v, %v); want = (nil, error)", user, err)
125+
}
126+
127+
user, err = client.GetUserByProviderID(context.Background(), "google.com", "a-uid-that-doesnt-exist")
128+
if user != nil || !auth.IsUserNotFound(err) {
129+
t.Errorf("GetUser(non.existing) = (%v, %v); want = (nil, error)", user, err)
130+
}
93131
}
94132

95133
func TestGetUsers(t *testing.T) {

0 commit comments

Comments
 (0)