@@ -30,8 +30,9 @@ import (
3030)
3131
3232const (
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.
222251func (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+
458569func 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+
501653type userQuery struct {
502654 field string
503655 value string
0 commit comments