Skip to content

Commit f1f98c4

Browse files
authored
Release 2.5.0 (#91)
* Renamed some tests and test parameters for clarity, and adhere to Go conventions (#74) * clean unused types (#76) * Create CHANGELOG.md (#75) (#79) * Create CHANGELOG.md Initial changelog based on https://firebase.google.com/support/release-notes/admin/go * change instance ID format (#82) Changing the format of the "non-existing" instance ID in the integration tests to comply with the expected iid format. * Import context from golang.org/x/net/ for 1.6 compatibility (#87) * import golang.org/x/net/context instead of context for 1.6 compatibility * Document non existing name in integration tests for iid (#85) * Revoke Tokens (#77) Adding TokensValidAfterMillis property, RevokeRefreshTokens(), and VerifyIDTokenAndCheckRevoked(). * Firebase Cloud Messaging API (#81) * Adding Firebase Cloud Messaging (#62) * initial commit for adding Firebase Cloud Messaging * add validator * use http const in messaging test * add client version header for stats * init integration test * add integration test (validated on IOS today) * add comment with URL to enable Firebase Cloud Messaging API * fix broken test * add integration tests * accept a Message instead of RequestMessage + and rename method + send / sendDryRun * update fcm url * rollback url endpoint * fix http constants, change responseMessage visibility, change map[string]interface{} as map[string]string * fix http constants * fix integration tests * fix APNS naming * add validators * Added APNS types; Updated tests * Added more tests; Fixed APNS serialization * Updated documentation * Improved error handling inFCM * Added utils file * Updated integration tests * Implemented topic management operations * Added integration tests * Updated CHANGELOG * Addressing code review comments * Supporting 0 valued Aps.Badge * Addressing some review comments * Removed some unused vars * Accepting prefixed topic names (#84) * Accepting prefixed topic named * Added a comment * Using new FCM error codes (#89) * Bumped version to 2.5.0 (#90)
1 parent 6274018 commit f1f98c4

23 files changed

+2000
-138
lines changed

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Unreleased
2-
-
2+
3+
-
4+
5+
# v2.5.0
6+
7+
- [changed] Import context from `golang.org/x/net` for 1.6 compatibility
8+
9+
### Cloud Messaging
10+
11+
- [added] Added the `messaging` package for sending Firebase notifications
12+
and managing topic subscriptions.
13+
14+
### Authentication
15+
16+
- [added] A new [`VerifyIDTokenAndCheckRevoked()`](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken)
17+
function has been added to check for revoked ID tokens.
18+
- [added] A new [`RevokeRefreshTokens()`](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens)
19+
function has been added to invalidate all refresh tokens issued to a user.
20+
- [added] A new property `TokensValidAfterMillis` has been added to the
21+
['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord)
22+
type, which stores the time of the revocation truncated to 1 second accuracy.
323

424
# v2.4.0
525

auth/auth.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,26 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac
177177
return encodeToken(c.snr, defaultHeader(), payload)
178178
}
179179

180+
// RevokeRefreshTokens revokes all refresh tokens issued to a user.
181+
//
182+
// RevokeRefreshTokens updates the user's TokensValidAfterMillis to the current UTC second.
183+
// It is important that the server on which this is called has its clock set correctly and synchronized.
184+
//
185+
// While this revokes all sessions for a specified user and disables any new ID tokens for existing sessions
186+
// from getting minted, existing ID tokens may remain active until their natural expiration (one hour).
187+
// To verify that ID tokens are revoked, use `verifyIdTokenAndCheckRevoked(ctx, idToken)`.
188+
func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error {
189+
return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshTokens())
190+
}
191+
180192
// VerifyIDToken verifies the signature and payload of the provided ID token.
181193
//
182194
// VerifyIDToken accepts a signed JWT token string, and verifies that it is current, issued for the
183195
// correct Firebase project, and signed by the Google Firebase services in the cloud. It returns
184196
// a Token containing the decoded claims in the input JWT. See
185197
// https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for
186198
// more details on how to obtain an ID token in a client app.
199+
// This does not check whether or not the token has been revoked. See `VerifyIDTokenAndCheckRevoked` below.
187200
func (c *Client) VerifyIDToken(idToken string) (*Token, error) {
188201
if c.projectID == "" {
189202
return nil, errors.New("project id not available")
@@ -237,6 +250,27 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) {
237250
return p, nil
238251
}
239252

253+
// VerifyIDTokenAndCheckRevoked verifies the provided ID token and checks it has not been revoked.
254+
//
255+
// VerifyIDTokenAndCheckRevoked verifies the signature and payload of the provided ID token and
256+
// checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT.
257+
func (c *Client) VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken string) (*Token, error) {
258+
p, err := c.VerifyIDToken(idToken)
259+
if err != nil {
260+
return nil, err
261+
}
262+
263+
user, err := c.GetUser(ctx, p.UID)
264+
if err != nil {
265+
return nil, err
266+
}
267+
268+
if p.IssuedAt*1000 < user.TokensValidAfterMillis {
269+
return nil, fmt.Errorf("ID token has been revoked")
270+
}
271+
return p, nil
272+
}
273+
240274
func parseKey(key string) (*rsa.PrivateKey, error) {
241275
block, _ := pem.Decode([]byte(key))
242276
if block == nil {

auth/auth_std.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package auth
1818

19-
import "context"
19+
import "golang.org/x/net/context"
2020

2121
func newSigner(ctx context.Context) (signer, error) {
2222
return serviceAcctSigner{}, nil

auth/auth_test.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ import (
3737
)
3838

3939
var client *Client
40+
var ctx context.Context
4041
var testIDToken string
4142
var testGetUserResponse []byte
4243
var testListUsersResponse []byte
43-
4444
var defaultTestOpts = []option.ClientOption{
4545
option.WithCredentialsFile("../testdata/service_account.json"),
4646
}
@@ -49,7 +49,6 @@ func TestMain(m *testing.M) {
4949
var (
5050
err error
5151
ks keySource
52-
ctx context.Context
5352
creds *google.DefaultCredentials
5453
opts []option.ClientOption
5554
)
@@ -193,6 +192,52 @@ func TestCustomTokenInvalidCredential(t *testing.T) {
193192
}
194193
}
195194

195+
func TestVerifyIDTokenAndCheckRevokedValid(t *testing.T) {
196+
s := echoServer(testGetUserResponse, t)
197+
defer s.Close()
198+
199+
ft, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, testIDToken)
200+
if err != nil {
201+
t.Error(err)
202+
}
203+
if ft.Claims["admin"] != true {
204+
t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"])
205+
}
206+
if ft.UID != ft.Subject {
207+
t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject)
208+
}
209+
}
210+
211+
func TestVerifyIDTokenAndCheckRevokedDoNotCheck(t *testing.T) {
212+
s := echoServer(testGetUserResponse, t)
213+
defer s.Close()
214+
tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token
215+
216+
ft, err := s.Client.VerifyIDToken(tok)
217+
if err != nil {
218+
t.Fatal(err)
219+
}
220+
if ft.Claims["admin"] != true {
221+
t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"])
222+
}
223+
if ft.UID != ft.Subject {
224+
t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject)
225+
}
226+
}
227+
228+
func TestVerifyIDTokenAndCheckRevokedInvalidated(t *testing.T) {
229+
s := echoServer(testGetUserResponse, t)
230+
defer s.Close()
231+
tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token
232+
233+
p, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, tok)
234+
we := "ID token has been revoked"
235+
if p != nil || err == nil || err.Error() != we {
236+
t.Errorf("VerifyIDTokenAndCheckRevoked(ctx, token) =(%v, %v); want = (%v, %v)",
237+
p, err, nil, we)
238+
}
239+
}
240+
196241
func TestVerifyIDToken(t *testing.T) {
197242
ft, err := client.VerifyIDToken(testIDToken)
198243
if err != nil {

auth/user_mgt.go

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"reflect"
2222
"regexp"
2323
"strings"
24+
"time"
2425

2526
"golang.org/x/net/context"
2627
"google.golang.org/api/identitytoolkit/v3"
@@ -39,6 +40,7 @@ var commonValidators = map[string]func(interface{}) error{
3940
"password": validatePassword,
4041
"photoUrl": validatePhotoURL,
4142
"localId": validateUID,
43+
"validSince": func(interface{}) error { return nil }, // Needed for preparePayload.
4244
}
4345

4446
// Create a new interface
@@ -65,6 +67,7 @@ type UserInfo struct {
6567
}
6668

6769
// UserMetadata contains additional metadata associated with a user account.
70+
// Timestamps are in milliseconds since epoch.
6871
type UserMetadata struct {
6972
CreationTimestamp int64
7073
LastLogInTimestamp int64
@@ -73,11 +76,12 @@ type UserMetadata struct {
7376
// UserRecord contains metadata associated with a Firebase user account.
7477
type UserRecord struct {
7578
*UserInfo
76-
CustomClaims map[string]interface{}
77-
Disabled bool
78-
EmailVerified bool
79-
ProviderUserInfo []*UserInfo
80-
UserMetadata *UserMetadata
79+
CustomClaims map[string]interface{}
80+
Disabled bool
81+
EmailVerified bool
82+
ProviderUserInfo []*UserInfo
83+
TokensValidAfterMillis int64 // milliseconds since epoch.
84+
UserMetadata *UserMetadata
8185
}
8286

8387
// ExportedUserRecord is the returned user value used when listing all the users.
@@ -173,6 +177,13 @@ func (u *UserToUpdate) PhoneNumber(phone string) *UserToUpdate { u.set("phoneNum
173177
// PhotoURL setter.
174178
func (u *UserToUpdate) PhotoURL(url string) *UserToUpdate { u.set("photoUrl", url); return u }
175179

180+
// revokeRefreshTokens revokes all refresh tokens for a user by setting the validSince property
181+
// to the present in epoch seconds.
182+
func (u *UserToUpdate) revokeRefreshTokens() *UserToUpdate {
183+
u.set("validSince", time.Now().Unix())
184+
return u
185+
}
186+
176187
// CreateUser creates a new user with the specified properties.
177188
func (c *Client) CreateUser(ctx context.Context, user *UserToCreate) (*UserRecord, error) {
178189
uid, err := c.createUser(ctx, user)
@@ -471,7 +482,12 @@ func (u *UserToUpdate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi
471482
if err := validate(v); err != nil {
472483
return err
473484
}
474-
reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)).SetString(params[key].(string))
485+
f := reflect.ValueOf(user).Elem().FieldByName(strings.Title(key))
486+
if f.Kind() == reflect.String {
487+
f.SetString(params[key].(string))
488+
} else if f.Kind() == reflect.Int64 {
489+
f.SetInt(params[key].(int64))
490+
}
475491
}
476492
}
477493
if params["disableUser"] != nil {
@@ -498,37 +514,6 @@ func (u *UserToUpdate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi
498514

499515
// End of validators
500516

501-
// Response Types -------------------------------
502-
503-
type getUserResponse struct {
504-
RequestType string
505-
Users []responseUserRecord
506-
}
507-
508-
type responseUserRecord struct {
509-
UID string
510-
DisplayName string
511-
Email string
512-
PhoneNumber string
513-
PhotoURL string
514-
CreationTimestamp int64
515-
LastLogInTimestamp int64
516-
ProviderID string
517-
CustomClaims string
518-
Disabled bool
519-
EmailVerified bool
520-
ProviderUserInfo []*UserInfo
521-
PasswordHash string
522-
PasswordSalt string
523-
ValidSince int64
524-
}
525-
526-
type listUsersResponse struct {
527-
RequestType string
528-
Users []responseUserRecord
529-
NextPage string
530-
}
531-
532517
// Helper functions for retrieval and HTTP calls.
533518

534519
func (c *Client) createUser(ctx context.Context, user *UserToCreate) (string, error) {
@@ -559,7 +544,6 @@ func (c *Client) updateUser(ctx context.Context, uid string, user *UserToUpdate)
559544
if user == nil || user.params == nil {
560545
return fmt.Errorf("update parameters must not be nil or empty")
561546
}
562-
563547
request := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{
564548
LocalId: uid,
565549
}
@@ -628,10 +612,11 @@ func makeExportedUser(r *identitytoolkit.UserInfo) (*ExportedUserRecord, error)
628612
ProviderID: defaultProviderID,
629613
UID: r.LocalId,
630614
},
631-
CustomClaims: cc,
632-
Disabled: r.Disabled,
633-
EmailVerified: r.EmailVerified,
634-
ProviderUserInfo: providerUserInfo,
615+
CustomClaims: cc,
616+
Disabled: r.Disabled,
617+
EmailVerified: r.EmailVerified,
618+
ProviderUserInfo: providerUserInfo,
619+
TokensValidAfterMillis: r.ValidSince * 1000,
635620
UserMetadata: &UserMetadata{
636621
LastLogInTimestamp: r.LastLoginAt,
637622
CreationTimestamp: r.CreatedAt,

0 commit comments

Comments
 (0)