@@ -30,8 +30,9 @@ import (
30
30
)
31
31
32
32
const (
33
- maxLenPayloadCC = 1000
34
- defaultProviderID = "firebase"
33
+ maxLenPayloadCC = 1000
34
+ defaultProviderID = "firebase"
35
+ idToolkitV1Endpoint = "https://identitytoolkit.googleapis.com/v1"
35
36
36
37
// Maximum number of users allowed to batch get at a time.
37
38
maxGetAccountsBatchSize = 100
@@ -217,6 +218,34 @@ func (u *UserToUpdate) PhotoURL(url string) *UserToUpdate {
217
218
return u .set ("photoUrl" , url )
218
219
}
219
220
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
+
220
249
// revokeRefreshTokens revokes all refresh tokens for a user by setting the validSince property
221
250
// to the present in epoch seconds.
222
251
func (u * UserToUpdate ) revokeRefreshTokens () * UserToUpdate {
@@ -296,6 +325,78 @@ func (u *UserToUpdate) validatedRequest() (map[string]interface{}, error) {
296
325
return nil , err
297
326
}
298
327
}
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
+
299
400
return req , nil
300
401
}
301
402
@@ -455,6 +556,16 @@ func validatePhone(phone string) error {
455
556
return nil
456
557
}
457
558
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
+
458
569
func validateProvider (providerID string , providerUID string ) error {
459
570
if providerID == "" {
460
571
return fmt .Errorf ("providerID must be a non-empty string" )
@@ -498,6 +609,47 @@ func (c *baseClient) GetUserByPhoneNumber(ctx context.Context, phone string) (*U
498
609
})
499
610
}
500
611
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
+
501
653
type userQuery struct {
502
654
field string
503
655
value string
0 commit comments