Skip to content

Commit eb0d2a0

Browse files
Merge dev into master
2 parents a957589 + 9161b8c commit eb0d2a0

File tree

5 files changed

+617
-48
lines changed

5 files changed

+617
-48
lines changed

auth/import_users.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,8 @@ func (u *UserToImport) validatedUserInfo() (map[string]interface{}, error) {
236236

237237
if providers, ok := info["providerUserInfo"]; ok {
238238
for _, p := range providers.([]*UserProvider) {
239-
if p.UID == "" {
240-
return nil, fmt.Errorf("user provdier must specify a uid")
241-
}
242-
if p.ProviderID == "" {
243-
return nil, fmt.Errorf("user provider must specify a provider ID")
239+
if err := validateProviderUserInfo(p); err != nil {
240+
return nil, err
244241
}
245242
}
246243
}

auth/user_mgt.go

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ import (
3030
)
3131

3232
const (
33-
maxLenPayloadCC = 1000
34-
defaultProviderID = "firebase"
33+
maxLenPayloadCC = 1000
34+
defaultProviderID = "firebase"
35+
idToolkitV1Endpoint = "https://identitytoolkit.googleapis.com/v1"
3536

3637
// Maximum number of users allowed to batch get at a time.
3738
maxGetAccountsBatchSize = 100
@@ -217,6 +218,34 @@ func (u *UserToUpdate) PhotoURL(url string) *UserToUpdate {
217218
return u.set("photoUrl", url)
218219
}
219220

221+
// ProviderToLink links this user to the specified provider.
222+
//
223+
// Linking a provider to an existing user account does not invalidate the
224+
// refresh token of that account. In other words, the existing account would
225+
// continue to be able to access resources, despite not having used the newly
226+
// linked provider to log in. If you wish to force the user to authenticate
227+
// with this new provider, you need to (a) revoke their refresh token (see
228+
// https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens),
229+
// and (b) ensure no other authentication methods are present on this account.
230+
func (u *UserToUpdate) ProviderToLink(userProvider *UserProvider) *UserToUpdate {
231+
return u.set("linkProviderUserInfo", userProvider)
232+
}
233+
234+
// ProvidersToDelete unlinks this user from the specified providers.
235+
func (u *UserToUpdate) ProvidersToDelete(providerIds []string) *UserToUpdate {
236+
// skip setting the value to empty if it's already empty.
237+
if len(providerIds) == 0 {
238+
if u.params == nil {
239+
return u
240+
}
241+
if _, ok := u.params["providersToDelete"]; !ok {
242+
return u
243+
}
244+
}
245+
246+
return u.set("providersToDelete", providerIds)
247+
}
248+
220249
// revokeRefreshTokens revokes all refresh tokens for a user by setting the validSince property
221250
// to the present in epoch seconds.
222251
func (u *UserToUpdate) revokeRefreshTokens() *UserToUpdate {
@@ -296,6 +325,78 @@ func (u *UserToUpdate) validatedRequest() (map[string]interface{}, error) {
296325
return nil, err
297326
}
298327
}
328+
329+
if linkProviderUserInfo, ok := req["linkProviderUserInfo"]; ok {
330+
userProvider := linkProviderUserInfo.(*UserProvider)
331+
if err := validateProviderUserInfo(userProvider); err != nil {
332+
return nil, err
333+
}
334+
335+
// Although we don't really advertise it, we want to also handle linking of
336+
// non-federated idps with this call. So if we detect one of them, we'll
337+
// adjust the properties parameter appropriately. This *does* imply that a
338+
// conflict could arise, e.g. if the user provides a phoneNumber property,
339+
// but also provides a providerToLink with a 'phone' provider id. In that
340+
// case, we'll return an error.
341+
342+
if userProvider.ProviderID == "email" {
343+
if _, ok := req["email"]; ok {
344+
// We could relax this to only return an error if the email addrs don't
345+
// match. But for now, we'll be extra picky.
346+
return nil, errors.New(
347+
"both UserToUpdate.Email and UserToUpdate.ProviderToLink.ProviderID='email' " +
348+
"were set; to link to the email/password provider, only specify the " +
349+
"UserToUpdate.Email field")
350+
}
351+
req["email"] = userProvider.UID
352+
delete(req, "linkProviderUserInfo")
353+
} else if userProvider.ProviderID == "phone" {
354+
if _, ok := req["phoneNumber"]; ok {
355+
// We could relax this to only return an error if the phone numbers don't
356+
// match. But for now, we'll be extra picky.
357+
return nil, errors.New(
358+
"both UserToUpdate.PhoneNumber and UserToUpdate.ProviderToLink.ProviderID='phone' " +
359+
"were set; to link to the phone provider, only specify the " +
360+
"UserToUpdate.PhoneNumber field")
361+
}
362+
req["phoneNumber"] = userProvider.UID
363+
delete(req, "linkProviderUserInfo")
364+
}
365+
}
366+
367+
if providersToDelete, ok := req["providersToDelete"]; ok {
368+
var deleteProvider []string
369+
list, ok := req["deleteProvider"]
370+
if ok {
371+
deleteProvider = list.([]string)
372+
}
373+
374+
for _, providerToDelete := range providersToDelete.([]string) {
375+
if providerToDelete == "" {
376+
return nil, errors.New("providersToDelete must not include empty strings")
377+
}
378+
379+
// If we've been told to unlink the phone provider both via setting
380+
// phoneNumber to "" *and* by setting providersToDelete to include
381+
// 'phone', then we'll reject that. Though it might also be reasonable to
382+
// relax this restriction and just unlink it.
383+
if providerToDelete == "phone" {
384+
for _, prov := range deleteProvider {
385+
if prov == "phone" {
386+
return nil, errors.New("both UserToUpdate.PhoneNumber='' and " +
387+
"UserToUpdate.ProvidersToDelete=['phone'] were set; to unlink from a " +
388+
"phone provider, only specify the UserToUpdate.PhoneNumber='' field")
389+
}
390+
}
391+
}
392+
393+
deleteProvider = append(deleteProvider, providerToDelete)
394+
}
395+
396+
req["deleteProvider"] = deleteProvider
397+
delete(req, "providersToDelete")
398+
}
399+
299400
return req, nil
300401
}
301402

@@ -455,6 +556,16 @@ func validatePhone(phone string) error {
455556
return nil
456557
}
457558

559+
func validateProviderUserInfo(p *UserProvider) error {
560+
if p.UID == "" {
561+
return fmt.Errorf("user provider must specify a uid")
562+
}
563+
if p.ProviderID == "" {
564+
return fmt.Errorf("user provider must specify a provider ID")
565+
}
566+
return nil
567+
}
568+
458569
func validateProvider(providerID string, providerUID string) error {
459570
if providerID == "" {
460571
return fmt.Errorf("providerID must be a non-empty string")
@@ -498,6 +609,47 @@ func (c *baseClient) GetUserByPhoneNumber(ctx context.Context, phone string) (*U
498609
})
499610
}
500611

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+
501653
type userQuery struct {
502654
field string
503655
value string

0 commit comments

Comments
 (0)