From cf9b75e6dbf60a9eea83ed6ace5461b6fc5acf54 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 13:15:24 -0500 Subject: [PATCH 01/31] mplementation of revoke --- testdata/get_user_valid_since_y2k2c.json | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 testdata/get_user_valid_since_y2k2c.json diff --git a/testdata/get_user_valid_since_y2k2c.json b/testdata/get_user_valid_since_y2k2c.json new file mode 100644 index 00000000..1bda85fa --- /dev/null +++ b/testdata/get_user_valid_since_y2k2c.json @@ -0,0 +1,36 @@ +{ + "kind": "identitytoolkit#GetAccountInfoResponse", + "users": [ + { + "localId": "testuser", + "email": "testuser@example.com", + "phoneNumber": "+1234567890", + "emailVerified": true, + "displayName": "Test User", + "providerUserInfo": [ + { + "providerId": "password", + "displayName": "Test User", + "photoUrl": "http://www.example.com/testuser/photo.png", + "federatedId": "testuser@example.com", + "email": "testuser@example.com", + "rawId": "testuid" + }, + { + "providerId": "phone", + "phoneNumber": "+1234567890", + "rawId": "testuid" + } + ], + "photoUrl": "http://www.example.com/testuser/photo.png", + "passwordHash": "passwordhash", + "salt": "salt===", + "passwordUpdatedAt": 1.494364393E+12, + "validSince": "7258118400", + "disabled": false, + "createdAt": "1234567890", + "lastLoginAt": "1233211232", + "customAttributes": "{\"admin\": true, \"package\": \"gold\"}" + } + ] +} From 8d967efe7952bf0ef348d1f14b98c98f70f45ac0 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 13:21:44 -0500 Subject: [PATCH 02/31] mplementation of revoke --- auth/auth.go | 29 ++++++++++++++++++++ auth/auth_test.go | 52 ++++++++++++++++++++++++++++++++++-- auth/jwt.go | 1 - auth/user_mgt.go | 45 +++++++++++++++++++++++-------- auth/user_mgt_test.go | 62 ++++++++++++++++++++++++++++++------------- 5 files changed, 157 insertions(+), 32 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index cc798182..24e01370 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,6 +177,16 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } +// RevokeRefreshToken revokes all tokens minted before the present. +func (c *Client) RevokeRefreshToken(ctx context.Context, idToken string) error { + h := &jwtHeader{} + p := &Token{} + if err := decodeToken(idToken, c.ks, h, p); err != nil { + return err + } + return c.updateUser(ctx, p.UID, (&UserToUpdate{}).revokeRefreshToken()) +} + // VerifyIDToken verifies the signature and payload of the provided ID token. // // VerifyIDToken accepts a signed JWT token string, and verifies that it is current, issued for the @@ -237,6 +247,25 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) { return p, nil } +// VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and +// if requested, whether it was revoked. +// see: VerifyIDToken above. +func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string, checkToken bool) (*Token, error) { + p, err := c.VerifyIDToken(idToken) + + if !checkToken || err != nil { + return p, err + } + user, err := c.GetUser(ctx, p.UID) + if err != nil { + return p, err + } + if p.IssuedAt < user.TokensValidAfterTime { + err = fmt.Errorf("the Firebase ID token has been revoked") + } + return p, err +} + func parseKey(key string) (*rsa.PrivateKey, error) { block, _ := pem.Decode([]byte(key)) if block == nil { diff --git a/auth/auth_test.go b/auth/auth_test.go index 79e957d8..c91cd266 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -37,10 +37,11 @@ import ( ) var client *Client +var ctx context.Context var testIDToken string var testGetUserResponse []byte +var testGetUserY2K2CResponse []byte var testListUsersResponse []byte - var defaultTestOpts = []option.ClientOption{ option.WithCredentialsFile("../testdata/service_account.json"), } @@ -49,7 +50,6 @@ func TestMain(m *testing.M) { var ( err error ks keySource - ctx context.Context creds *google.DefaultCredentials opts []option.ClientOption ) @@ -90,6 +90,11 @@ func TestMain(m *testing.M) { log.Fatalln(err) } + testGetUserY2K2CResponse, err = ioutil.ReadFile("../testdata/get_user_valid_since_y2k2c.json") + if err != nil { + log.Fatalln(err) + } + testListUsersResponse, err = ioutil.ReadFile("../testdata/list_users.json") if err != nil { log.Fatalln(err) @@ -193,6 +198,49 @@ func TestCustomTokenInvalidCredential(t *testing.T) { } } +func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { + s := echoServer(testGetUserResponse, t) + defer s.Close() + + ft, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, testIDToken, false) + if err != nil { + t.Fatal(err) + } + if ft.Claims["admin"] != true { + t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"]) + } + if ft.UID != ft.Subject { + t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject) + } +} + +func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { + s := echoServer(testGetUserResponse, t) + defer s.Close() + + ft, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, testIDToken, true) + if err != nil { + t.Error(err) + } + if ft.Claims["admin"] != true { + t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"]) + } + if ft.UID != ft.Subject { + t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject) + } +} +func TestVerifyIDTokenWithCheckRevokedInvalidatedY2K2C(t *testing.T) { + s := echoServer(testGetUserY2K2CResponse, t) + defer s.Close() + _, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, + getIDToken(mockIDTokenPayload{"uid": "uid"}), + true) + we := "the Firebase ID token has been revoked" + if err == nil || err.Error() != we { + t.Errorf("VerifyIDTokenWithCheckRevoked(..., token, true) = %v; want = %v", err, we) + } +} + func TestVerifyIDToken(t *testing.T) { ft, err := client.VerifyIDToken(testIDToken) if err != nil { diff --git a/auth/jwt.go b/auth/jwt.go index 42b5b7bb..74c1115b 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -113,7 +113,6 @@ func decodeToken(token string, ks keySource, h *jwtHeader, p jwtPayload) error { if err := p.decode(s[1]); err != nil { return err } - keys, err := ks.Keys() if err != nil { return err diff --git a/auth/user_mgt.go b/auth/user_mgt.go index eb92d9a5..10597ce3 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -21,6 +21,7 @@ import ( "reflect" "regexp" "strings" + "time" "golang.org/x/net/context" "google.golang.org/api/identitytoolkit/v3" @@ -39,6 +40,7 @@ var commonValidators = map[string]func(interface{}) error{ "password": validatePassword, "photoUrl": validatePhotoURL, "localId": validateUID, + "validSince": validateValidSince, } // Create a new interface @@ -73,11 +75,12 @@ type UserMetadata struct { // UserRecord contains metadata associated with a Firebase user account. type UserRecord struct { *UserInfo - CustomClaims map[string]interface{} - Disabled bool - EmailVerified bool - ProviderUserInfo []*UserInfo - UserMetadata *UserMetadata + CustomClaims map[string]interface{} + Disabled bool + EmailVerified bool + ProviderUserInfo []*UserInfo + TokensValidAfterTime int64 + UserMetadata *UserMetadata } // ExportedUserRecord is the returned user value used when listing all the users. @@ -173,6 +176,11 @@ func (u *UserToUpdate) PhoneNumber(phone string) *UserToUpdate { u.set("phoneNum // PhotoURL setter. func (u *UserToUpdate) PhotoURL(url string) *UserToUpdate { u.set("photoUrl", url); return u } +func (u *UserToUpdate) revokeRefreshToken() *UserToUpdate { + u.set("validSince", time.Now().Unix()) + return u +} + // CreateUser creates a new user with the specified properties. func (c *Client) CreateUser(ctx context.Context, user *UserToCreate) (*UserRecord, error) { uid, err := c.createUser(ctx, user) @@ -416,6 +424,10 @@ func validatePhone(val interface{}) error { return nil } +func validateValidSince(val interface{}) error { + return nil +} + func (u *UserToCreate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyingpartySignupNewUserRequest) error { params := map[string]interface{}{} if u.params == nil { @@ -430,7 +442,12 @@ func (u *UserToCreate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi if err := validate(v); err != nil { return err } - reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)).SetString(params[key].(string)) + f := reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)) + if f.Kind() == reflect.String { + f.SetString(params[key].(string)) + } else if f.Kind() == reflect.Int64 { + f.SetInt(params[key].(int64)) + } } } if params["disabled"] != nil { @@ -471,7 +488,12 @@ func (u *UserToUpdate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi if err := validate(v); err != nil { return err } - reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)).SetString(params[key].(string)) + f := reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)) + if f.Kind() == reflect.String { + f.SetString(params[key].(string)) + } else if f.Kind() == reflect.Int64 { + f.SetInt(params[key].(int64)) + } } } if params["disableUser"] != nil { @@ -597,10 +619,11 @@ func makeExportedUser(r *identitytoolkit.UserInfo) (*ExportedUserRecord, error) ProviderID: defaultProviderID, UID: r.LocalId, }, - CustomClaims: cc, - Disabled: r.Disabled, - EmailVerified: r.EmailVerified, - ProviderUserInfo: providerUserInfo, + CustomClaims: cc, + Disabled: r.Disabled, + EmailVerified: r.EmailVerified, + ProviderUserInfo: providerUserInfo, + TokensValidAfterTime: r.ValidSince, UserMetadata: &UserMetadata{ LastLogInTimestamp: r.LastLoginAt, CreationTimestamp: r.CreatedAt, diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 5c75fdf9..84eff442 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -24,6 +24,7 @@ import ( "reflect" "strings" "testing" + "time" "firebase.google.com/go/internal" @@ -59,6 +60,7 @@ var testUser = &UserRecord{ UID: "testuid", }, }, + TokensValidAfterTime: 1494364393, UserMetadata: &UserMetadata{ CreationTimestamp: 1234567890, LastLogInTimestamp: 1233211232, @@ -496,6 +498,27 @@ func TestUpdateUser(t *testing.T) { } } } +func TestRevokeRefreshToken(t *testing.T) { + resp := `{ + "kind": "identitytoolkit#SetAccountInfoResponse", + "localId": "expectedUserID" + }` + s := echoServer([]byte(resp), t) + defer s.Close() + before := time.Now().Unix() + tok := getIDToken(mockIDTokenPayload{"uid": "uid"}) + err := s.Client.RevokeRefreshToken(ctx, tok) + after := time.Now().Unix() + + req := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{} + err = json.Unmarshal(s.Rbody, &req) + if err != nil { + t.Error(err) + } + if req.ValidSince > after || req.ValidSince < before { + t.Errorf("validSince = %d, expecting time between %d and %d", req.ValidSince, before, after) + } +} func TestInvalidSetCustomClaims(t *testing.T) { cases := []struct { @@ -673,15 +696,7 @@ type mockAuthServer struct { Client *Client } -// echoServer takes either a []byte or a string filename, or an object. -// -// echoServer returns a server whose client will reply with depending on the input type: -// * []byte: the []byte it got -// * object: the marshalled object, in []byte form -// * nil: "{}" empty json, in case we aren't interested in the returned value, just the marshalled request -// The marshalled request is available through s.rbody, s being the retuned server. -// It also returns a closing functions that has to be defer closed. -func echoServer(resp interface{}, t *testing.T) *mockAuthServer { +func getEchoResponse(resp interface{}, t *testing.T) []byte { var b []byte var err error switch v := resp.(type) { @@ -690,12 +705,23 @@ func echoServer(resp interface{}, t *testing.T) *mockAuthServer { case []byte: b = v default: - b, err = json.Marshal(resp) - if err != nil { + if b, err = json.Marshal(resp); err != nil { t.Fatal("marshaling error") } } - s := mockAuthServer{Resp: b} + return b +} + +// echoServer takes either a []byte or a string filename, or an object. +// +// echoServer returns a server whose client will reply with depending on the input type: +// * []byte: the []byte it got +// * object: the marshalled object, in []byte form +// * nil: "{}" empty json, in case we aren't interested in the returned value, just the marshalled request +// The marshalled request is available through s.rbody, s being the retuned server. +// It also returns a closing functions that has to be defer closed. +func echoServer(resp interface{}, t *testing.T) *mockAuthServer { + s := mockAuthServer{Resp: getEchoResponse(resp, t)} const testToken = "test.token" const testVersion = "test.version" @@ -729,17 +755,17 @@ func echoServer(resp interface{}, t *testing.T) *mockAuthServer { } w.Header().Set("Content-Type", "application/json") w.Write(s.Resp) - }) s.Srv = httptest.NewServer(handler) - conf := &internal.AuthConfig{ Opts: []option.ClientOption{ - option.WithTokenSource(&mockTokenSource{testToken}), - }, - Version: testVersion, + option.WithTokenSource(&mockTokenSource{testToken})}, + ProjectID: "mock-project-id", + Version: testVersion, } - authClient, err := NewClient(context.Background(), conf) + + authClient, err := NewClient(ctx, conf) + authClient.ks = &fileKeySource{FilePath: "../testdata/public_certs.json"} if err != nil { t.Fatal(err) } From e8285390492521cab3352daeaf10c46f14b48abf Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 13:38:43 -0500 Subject: [PATCH 03/31] whitespace --- auth/auth_test.go | 4 ++-- auth/jwt.go | 1 + auth/user_mgt_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index c91cd266..b884c44c 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -218,7 +218,7 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() - ft, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, testIDToken, true) + ft, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, testIDToken, true) if err != nil { t.Error(err) } @@ -232,7 +232,7 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { func TestVerifyIDTokenWithCheckRevokedInvalidatedY2K2C(t *testing.T) { s := echoServer(testGetUserY2K2CResponse, t) defer s.Close() - _, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, + _, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, getIDToken(mockIDTokenPayload{"uid": "uid"}), true) we := "the Firebase ID token has been revoked" diff --git a/auth/jwt.go b/auth/jwt.go index 74c1115b..42b5b7bb 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -113,6 +113,7 @@ func decodeToken(token string, ks keySource, h *jwtHeader, p jwtPayload) error { if err := p.decode(s[1]); err != nil { return err } + keys, err := ks.Keys() if err != nil { return err diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 84eff442..28618dda 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -507,7 +507,7 @@ func TestRevokeRefreshToken(t *testing.T) { defer s.Close() before := time.Now().Unix() tok := getIDToken(mockIDTokenPayload{"uid": "uid"}) - err := s.Client.RevokeRefreshToken(ctx, tok) + err := s.Client.RevokeRefreshToken(nil, tok) after := time.Now().Unix() req := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{} From 1e0db763d8f0d0d8e53a85989ac3773d78f49c49 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 13:41:52 -0500 Subject: [PATCH 04/31] undo echo refactor --- auth/user_mgt_test.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 28618dda..27871625 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -696,7 +696,15 @@ type mockAuthServer struct { Client *Client } -func getEchoResponse(resp interface{}, t *testing.T) []byte { +// echoServer takes either a []byte or a string filename, or an object. +// +// echoServer returns a server whose client will reply with depending on the input type: +// * []byte: the []byte it got +// * object: the marshalled object, in []byte form +// * nil: "{}" empty json, in case we aren't interested in the returned value, just the marshalled request +// The marshalled request is available through s.rbody, s being the retuned server. +// It also returns a closing functions that has to be defer closed. +func echoServer(resp interface{}, t *testing.T) *mockAuthServer { var b []byte var err error switch v := resp.(type) { @@ -709,19 +717,7 @@ func getEchoResponse(resp interface{}, t *testing.T) []byte { t.Fatal("marshaling error") } } - return b -} - -// echoServer takes either a []byte or a string filename, or an object. -// -// echoServer returns a server whose client will reply with depending on the input type: -// * []byte: the []byte it got -// * object: the marshalled object, in []byte form -// * nil: "{}" empty json, in case we aren't interested in the returned value, just the marshalled request -// The marshalled request is available through s.rbody, s being the retuned server. -// It also returns a closing functions that has to be defer closed. -func echoServer(resp interface{}, t *testing.T) *mockAuthServer { - s := mockAuthServer{Resp: getEchoResponse(resp, t)} + s := mockAuthServer{Resp: b} const testToken = "test.token" const testVersion = "test.version" From d046362b93136b2a654afea237d0f540cb2eee75 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 13:51:46 -0500 Subject: [PATCH 05/31] undo reflect tests for create --- auth/user_mgt.go | 7 +------ integration/auth/auth_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 10597ce3..77daea23 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -442,12 +442,7 @@ func (u *UserToCreate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyi if err := validate(v); err != nil { return err } - f := reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)) - if f.Kind() == reflect.String { - f.SetString(params[key].(string)) - } else if f.Kind() == reflect.Int64 { - f.SetInt(params[key].(int64)) - } + reflect.ValueOf(user).Elem().FieldByName(strings.Title(key)).SetString(params[key].(string)) } } if params["disabled"] != nil { diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index ee0dc0b3..d81dbeda 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -77,6 +77,27 @@ func TestCustomToken(t *testing.T) { } } +func TestCustomTokenVerifyCheckIgnored(t *testing.T) { + ct, err := client.CustomToken("user1") + + if err != nil { + t.Fatal(err) + } + + idt, err := signInWithCustomToken(ct) + if err != nil { + t.Fatal(err) + } + + vt, err := client.VerifyIDToken(idt) + if err != nil { + t.Fatal(err) + } + if vt.UID != "user1" { + t.Errorf("UID = %q; want UID = %q", vt.UID, "user1") + } +} + func TestCustomTokenWithClaims(t *testing.T) { ct, err := client.CustomTokenWithClaims("user1", map[string]interface{}{ "premium": true, From de72ac51047bf704cefe452e073c38abcafb4a4e Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 13:57:29 -0500 Subject: [PATCH 06/31] test valid time --- auth/user_mgt.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 77daea23..3de27d64 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -425,6 +425,13 @@ func validatePhone(val interface{}) error { } func validateValidSince(val interface{}) error { + validSince := val.(int64) + if validSince > time.Now().Unix() { + return fmt.Errorf("timestamp cannot be in the future") + } + if validSince < time.Now().Unix()-3600 { + return fmt.Errorf("timestamp cannot be old") + } return nil } From 148fd88a58d71221fcdd779320c718e027b6f46c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 25 Jan 2018 15:57:37 -0500 Subject: [PATCH 07/31] integration tests --- auth/auth.go | 7 +++--- auth/user_mgt.go | 1 - integration/auth/auth_test.go | 37 +++++++++++++++++++++++++++++-- integration/auth/user_mgt_test.go | 15 +++++++++++-- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 24e01370..188ec7d2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -179,12 +179,11 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac // RevokeRefreshToken revokes all tokens minted before the present. func (c *Client) RevokeRefreshToken(ctx context.Context, idToken string) error { - h := &jwtHeader{} - p := &Token{} - if err := decodeToken(idToken, c.ks, h, p); err != nil { + vt, err := c.VerifyIDToken(idToken) + if err != nil { return err } - return c.updateUser(ctx, p.UID, (&UserToUpdate{}).revokeRefreshToken()) + return c.updateUser(ctx, vt.UID, (&UserToUpdate{}).revokeRefreshToken()) } // VerifyIDToken verifies the signature and payload of the provided ID token. diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 3de27d64..d64ad3aa 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -552,7 +552,6 @@ func (c *Client) updateUser(ctx context.Context, uid string, user *UserToUpdate) if user == nil || user.params == nil { return fmt.Errorf("update parameters must not be nil or empty") } - request := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{ LocalId: uid, } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index d81dbeda..a6699cb3 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -25,6 +25,7 @@ import ( "net/http" "os" "testing" + "time" "firebase.google.com/go/auth" "firebase.google.com/go/integration/internal" @@ -77,7 +78,7 @@ func TestCustomToken(t *testing.T) { } } -func TestCustomTokenVerifyCheckIgnored(t *testing.T) { +func TestCustomTokenVerifyCheckRevokedIgnored(t *testing.T) { ct, err := client.CustomToken("user1") if err != nil { @@ -89,7 +90,7 @@ func TestCustomTokenVerifyCheckIgnored(t *testing.T) { t.Fatal(err) } - vt, err := client.VerifyIDToken(idt) + vt, err := client.VerifyIDTokenWithCheckRevoked(context.Background(), idt, false) if err != nil { t.Fatal(err) } @@ -97,6 +98,38 @@ func TestCustomTokenVerifyCheckIgnored(t *testing.T) { t.Errorf("UID = %q; want UID = %q", vt.UID, "user1") } } +func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { + revokedId := "user_revoked" + ct, err := client.CustomToken(revokedId) + + if err != nil { + t.Fatal(err) + } + + idt, err := signInWithCustomToken(ct) + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + vt, err := client.VerifyIDTokenWithCheckRevoked(ctx, idt, true) + if err != nil { + t.Fatal(err) + } + if vt.UID != revokedId { + t.Errorf("UID = %q; want UID = %q", vt.UID, revokedId) + } + time.Sleep(time.Second) + if err = client.RevokeRefreshToken(ctx, idt); err != nil { + t.Fatal(err) + } + + vt, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, true) + we := "the Firebase ID token has been revoked" + if err == nil || err.Error() != we { + t.Errorf("VerifyIDTokenWithCheckRevoked; err = %s; want err = %v", err, we) + } + err = client.DeleteUser(ctx, revokedId) +} func TestCustomTokenWithClaims(t *testing.T) { ct, err := client.CustomTokenWithClaims("user1", map[string]interface{}{ diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index 8227599f..3a0cc2e1 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -19,6 +19,7 @@ import ( "fmt" "reflect" "testing" + "time" "google.golang.org/api/iterator" @@ -65,6 +66,14 @@ func testCreateUsers(t *testing.T) { if err != nil { t.Fatal(err) } + // make sure that the user.TokensValidAvterTime is not in the future or stale. + if u.TokensValidAfterTime > time.Now().Unix() { + t.Errorf("timestamp cannot be in the future") + } + if u.TokensValidAfterTime < time.Now().Unix()-3600 { + t.Errorf("timestamp cannot be old") + } + testFixtures.sampleUserBlank = u testFixtures.uidList = append(testFixtures.uidList, u.UID) @@ -216,12 +225,13 @@ func testUpdateUser(t *testing.T) { UID: testFixtures.sampleUserBlank.UID, ProviderID: "firebase", }, + TokensValidAfterTime: u.TokensValidAfterTime, UserMetadata: &auth.UserMetadata{ CreationTimestamp: testFixtures.sampleUserBlank.UserMetadata.CreationTimestamp, }, } if !reflect.DeepEqual(u, want) { - t.Errorf("GetUser() = %v; want = %v", u, want) + t.Errorf("GetUser() = %#v; want = %#v", u, want) } params := (&auth.UserToUpdate{}). @@ -247,6 +257,7 @@ func testUpdateUser(t *testing.T) { ProviderID: "firebase", Email: "abc@ab.ab", }, + TokensValidAfterTime: u.TokensValidAfterTime, UserMetadata: &auth.UserMetadata{ CreationTimestamp: testFixtures.sampleUserBlank.UserMetadata.CreationTimestamp, }, @@ -289,7 +300,7 @@ func testUpdateUser(t *testing.T) { // now compare the rest of the record, without the ProviderInfo u.ProviderUserInfo = nil if !reflect.DeepEqual(u, want) { - t.Errorf("UpdateUser() = %v; want = %v", u, want) + t.Errorf("UpdateUser() = %#v; want = %#v", u, want) } } From 6b02152a52d29df2b861ed7e1b484c05d8cf871f Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 26 Jan 2018 11:43:48 -0500 Subject: [PATCH 08/31] lint --- integration/auth/auth_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index a6699cb3..30ab9b3d 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -99,8 +99,8 @@ func TestCustomTokenVerifyCheckRevokedIgnored(t *testing.T) { } } func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { - revokedId := "user_revoked" - ct, err := client.CustomToken(revokedId) + revokedID := "user_revoked" + ct, err := client.CustomToken(revokedID) if err != nil { t.Fatal(err) @@ -115,8 +115,8 @@ func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { if err != nil { t.Fatal(err) } - if vt.UID != revokedId { - t.Errorf("UID = %q; want UID = %q", vt.UID, revokedId) + if vt.UID != revokedID { + t.Errorf("UID = %q; want UID = %q", vt.UID, revokedID) } time.Sleep(time.Second) if err = client.RevokeRefreshToken(ctx, idt); err != nil { @@ -128,7 +128,7 @@ func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { if err == nil || err.Error() != we { t.Errorf("VerifyIDTokenWithCheckRevoked; err = %s; want err = %v", err, we) } - err = client.DeleteUser(ctx, revokedId) + err = client.DeleteUser(ctx, revokedID) } func TestCustomTokenWithClaims(t *testing.T) { From f199257b4ee0439425f7ad799465706cc4a4683c Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 26 Jan 2018 11:49:10 -0500 Subject: [PATCH 09/31] comment --- auth/auth.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auth/auth.go b/auth/auth.go index 188ec7d2..6ab8987f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,7 +177,9 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } -// RevokeRefreshToken revokes all tokens minted before the present. +// RevokeRefreshToken revokes all tokens minted before the current second. +// Note that if there is a possibility that a minted token it will be revoked in the same second +// as it was created, the revoke will fail. In that case, sleep for 1 second before revoking. func (c *Client) RevokeRefreshToken(ctx context.Context, idToken string) error { vt, err := c.VerifyIDToken(idToken) if err != nil { From c789e3ef38db1b453f9abb5b6edd19db75e352dc Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 26 Jan 2018 11:52:59 -0500 Subject: [PATCH 10/31] comment --- auth/auth.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 6ab8987f..5b272a5e 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,9 +177,13 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } -// RevokeRefreshToken revokes all tokens minted before the current second. -// Note that if there is a possibility that a minted token it will be revoked in the same second -// as it was created, the revoke will fail. In that case, sleep for 1 second before revoking. +// RevokeRefreshToken revokes all refresh tokens for the specified user identified by the uid provided. +// In addition to revoking all refresh tokens for a user, all ID tokens issued +// before revocation will also be revoked on the Auth backend. Any request with an +// ID token generated before revocation will be rejected with a token expired error. +// Note that due to the fact that the timestamp is stored in seconds, any tokens minted in +// the same second as the revokation will still be valid. If there is a chance that a token +// was minted in the last second, delay for 1 second before revoking.all tokens minted before the current second. func (c *Client) RevokeRefreshToken(ctx context.Context, idToken string) error { vt, err := c.VerifyIDToken(idToken) if err != nil { From bfb6a01d58ffaa19d9d1d750caf6cd1f6a5a0ce2 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 26 Jan 2018 16:01:57 -0500 Subject: [PATCH 11/31] typo --- auth/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/auth.go b/auth/auth.go index 5b272a5e..af450117 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -182,7 +182,7 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac // before revocation will also be revoked on the Auth backend. Any request with an // ID token generated before revocation will be rejected with a token expired error. // Note that due to the fact that the timestamp is stored in seconds, any tokens minted in -// the same second as the revokation will still be valid. If there is a chance that a token +// the same second as the revocation will still be valid. If there is a chance that a token // was minted in the last second, delay for 1 second before revoking.all tokens minted before the current second. func (c *Client) RevokeRefreshToken(ctx context.Context, idToken string) error { vt, err := c.VerifyIDToken(idToken) From 48910adbbb1912a32ead6f37392eb4f74a5247be Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sun, 28 Jan 2018 09:31:30 -0500 Subject: [PATCH 12/31] clean redundant property --- auth/user_mgt_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 27871625..b1046f7d 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -506,7 +506,7 @@ func TestRevokeRefreshToken(t *testing.T) { s := echoServer([]byte(resp), t) defer s.Close() before := time.Now().Unix() - tok := getIDToken(mockIDTokenPayload{"uid": "uid"}) + tok := getIDToken(mockIDTokenPayload{}) err := s.Client.RevokeRefreshToken(nil, tok) after := time.Now().Unix() From 1192a1c3dc01e7ad51f2f0dc20b356c1c5ecf84d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Sun, 28 Jan 2018 19:00:00 -0500 Subject: [PATCH 13/31] change test for invalid token --- auth/auth_test.go | 18 ++++++------ testdata/get_user_valid_since_y2k2c.json | 36 ------------------------ 2 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 testdata/get_user_valid_since_y2k2c.json diff --git a/auth/auth_test.go b/auth/auth_test.go index b884c44c..fe7b8736 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -40,7 +40,6 @@ var client *Client var ctx context.Context var testIDToken string var testGetUserResponse []byte -var testGetUserY2K2CResponse []byte var testListUsersResponse []byte var defaultTestOpts = []option.ClientOption{ option.WithCredentialsFile("../testdata/service_account.json"), @@ -90,11 +89,6 @@ func TestMain(m *testing.M) { log.Fatalln(err) } - testGetUserY2K2CResponse, err = ioutil.ReadFile("../testdata/get_user_valid_since_y2k2c.json") - if err != nil { - log.Fatalln(err) - } - testListUsersResponse, err = ioutil.ReadFile("../testdata/list_users.json") if err != nil { log.Fatalln(err) @@ -229,11 +223,15 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject) } } -func TestVerifyIDTokenWithCheckRevokedInvalidatedY2K2C(t *testing.T) { - s := echoServer(testGetUserY2K2CResponse, t) +func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { + s := echoServer(testGetUserResponse, t) defer s.Close() - _, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, - getIDToken(mockIDTokenPayload{"uid": "uid"}), + u, err := s.Client.GetUser(ctx, "testuser") + if err != nil { + t.Fatal("Error retrieving user") + } + _, err = s.Client.VerifyIDTokenWithCheckRevoked(ctx, + getIDToken(mockIDTokenPayload{"uid": "uid", "iat": int(u.TokensValidAfterTime - 10)}), true) we := "the Firebase ID token has been revoked" if err == nil || err.Error() != we { diff --git a/testdata/get_user_valid_since_y2k2c.json b/testdata/get_user_valid_since_y2k2c.json deleted file mode 100644 index 1bda85fa..00000000 --- a/testdata/get_user_valid_since_y2k2c.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "kind": "identitytoolkit#GetAccountInfoResponse", - "users": [ - { - "localId": "testuser", - "email": "testuser@example.com", - "phoneNumber": "+1234567890", - "emailVerified": true, - "displayName": "Test User", - "providerUserInfo": [ - { - "providerId": "password", - "displayName": "Test User", - "photoUrl": "http://www.example.com/testuser/photo.png", - "federatedId": "testuser@example.com", - "email": "testuser@example.com", - "rawId": "testuid" - }, - { - "providerId": "phone", - "phoneNumber": "+1234567890", - "rawId": "testuid" - } - ], - "photoUrl": "http://www.example.com/testuser/photo.png", - "passwordHash": "passwordhash", - "salt": "salt===", - "passwordUpdatedAt": 1.494364393E+12, - "validSince": "7258118400", - "disabled": false, - "createdAt": "1234567890", - "lastLoginAt": "1233211232", - "customAttributes": "{\"admin\": true, \"package\": \"gold\"}" - } - ] -} From 5b8297065722e98eba343c28fb86398d8fd55a96 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 29 Jan 2018 10:54:25 -0500 Subject: [PATCH 14/31] validator --- auth/user_mgt.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/auth/user_mgt.go b/auth/user_mgt.go index d64ad3aa..d2645379 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -425,12 +425,8 @@ func validatePhone(val interface{}) error { } func validateValidSince(val interface{}) error { - validSince := val.(int64) - if validSince > time.Now().Unix() { - return fmt.Errorf("timestamp cannot be in the future") - } - if validSince < time.Now().Unix()-3600 { - return fmt.Errorf("timestamp cannot be old") + if _, ok := val.(int64); !ok { + return fmt.Errorf("tokens valid after time must be an integer") } return nil } From 7e72fdcc4a07400133ae6622ac1c517d1f464aca Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 29 Jan 2018 10:59:16 -0500 Subject: [PATCH 15/31] add test --- integration/auth/auth_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 30ab9b3d..542ff9d6 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -128,7 +128,14 @@ func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { if err == nil || err.Error() != we { t.Errorf("VerifyIDTokenWithCheckRevoked; err = %s; want err = %v", err, we) } + _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, false) + if err != nil { + t.Errorf("VerifyIDTokenWithCheckRevoked(.., false); err = %s; want err = ", err) + } err = client.DeleteUser(ctx, revokedID) + if err != nil { + t.Error(err) + } } func TestCustomTokenWithClaims(t *testing.T) { From f859f6a7f6200bda148da400cf98e674bd3d1033 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 29 Jan 2018 12:00:21 -0500 Subject: [PATCH 16/31] revoke takes UID --- auth/auth.go | 8 ++------ auth/user_mgt_test.go | 20 ++++++++++++++++++-- integration/auth/auth_test.go | 35 +++++++++++++---------------------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index af450117..23ccd842 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -184,12 +184,8 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac // Note that due to the fact that the timestamp is stored in seconds, any tokens minted in // the same second as the revocation will still be valid. If there is a chance that a token // was minted in the last second, delay for 1 second before revoking.all tokens minted before the current second. -func (c *Client) RevokeRefreshToken(ctx context.Context, idToken string) error { - vt, err := c.VerifyIDToken(idToken) - if err != nil { - return err - } - return c.updateUser(ctx, vt.UID, (&UserToUpdate{}).revokeRefreshToken()) +func (c *Client) RevokeRefreshToken(ctx context.Context, uid string) error { + return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshToken()) } // VerifyIDToken verifies the signature and payload of the provided ID token. diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index b1046f7d..73393601 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -506,8 +506,10 @@ func TestRevokeRefreshToken(t *testing.T) { s := echoServer([]byte(resp), t) defer s.Close() before := time.Now().Unix() - tok := getIDToken(mockIDTokenPayload{}) - err := s.Client.RevokeRefreshToken(nil, tok) + err := s.Client.RevokeRefreshToken(nil, "some_uid") + if err != nil { + t.Error(err) + } after := time.Now().Unix() req := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{} @@ -520,6 +522,20 @@ func TestRevokeRefreshToken(t *testing.T) { } } +func TestRevokeRefreshTokenInvalidUID(t *testing.T) { + resp := `{ + "kind": "identitytoolkit#SetAccountInfoResponse", + "localId": "expectedUserID" + }` + s := echoServer([]byte(resp), t) + defer s.Close() + + we := "uid must not be empty" + if err := s.Client.RevokeRefreshToken(nil, ""); err.Error() != we { + t.Errorf("RevokeRefreshToken(); err = %s; want err = %s", err.Error(), we) + } +} + func TestInvalidSetCustomClaims(t *testing.T) { cases := []struct { cc map[string]interface{} diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 542ff9d6..25b31772 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -78,27 +78,7 @@ func TestCustomToken(t *testing.T) { } } -func TestCustomTokenVerifyCheckRevokedIgnored(t *testing.T) { - ct, err := client.CustomToken("user1") - - if err != nil { - t.Fatal(err) - } - - idt, err := signInWithCustomToken(ct) - if err != nil { - t.Fatal(err) - } - - vt, err := client.VerifyIDTokenWithCheckRevoked(context.Background(), idt, false) - if err != nil { - t.Fatal(err) - } - if vt.UID != "user1" { - t.Errorf("UID = %q; want UID = %q", vt.UID, "user1") - } -} -func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { +func TestCustomTokenVerifyCheckRevoked(t *testing.T) { revokedID := "user_revoked" ct, err := client.CustomToken(revokedID) @@ -119,7 +99,7 @@ func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { t.Errorf("UID = %q; want UID = %q", vt.UID, revokedID) } time.Sleep(time.Second) - if err = client.RevokeRefreshToken(ctx, idt); err != nil { + if err = client.RevokeRefreshToken(ctx, revokedID); err != nil { t.Fatal(err) } @@ -128,6 +108,8 @@ func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { if err == nil || err.Error() != we { t.Errorf("VerifyIDTokenWithCheckRevoked; err = %s; want err = %v", err, we) } + + // Does not return error if it isn't checked _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, false) if err != nil { t.Errorf("VerifyIDTokenWithCheckRevoked(.., false); err = %s; want err = ", err) @@ -136,6 +118,15 @@ func TestCustomTokenVerifyCheckRevokedChecked(t *testing.T) { if err != nil { t.Error(err) } + + // Sign in after revocation after revocation. + if idt, err = signInWithCustomToken(ct); err != nil { + t.Fatal(err) + } + + if _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, true); err != nil { + t.Errorf("VerifyIDTokenWithCheckRevoked(); err = %s; want err = ", err) + } } func TestCustomTokenWithClaims(t *testing.T) { From 42e5d8dc539bff1c67bdeb43882de560b036ef6d Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 29 Jan 2018 12:07:40 -0500 Subject: [PATCH 17/31] test revoked but not checked --- auth/auth_test.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index fe7b8736..090fdc68 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -192,13 +192,13 @@ func TestCustomTokenInvalidCredential(t *testing.T) { } } -func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { +func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() - ft, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, testIDToken, false) + ft, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, testIDToken, true) if err != nil { - t.Fatal(err) + t.Error(err) } if ft.Claims["admin"] != true { t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"]) @@ -208,13 +208,19 @@ func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { } } -func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { +func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() + u, err := s.Client.GetUser(ctx, "testuser") + if err != nil { + t.Fatal("Error retrieving user") + } - ft, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, testIDToken, true) + ft, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, + getIDToken(mockIDTokenPayload{"uid": "uid", "iat": int(u.TokensValidAfterTime - 10)}), + false) if err != nil { - t.Error(err) + t.Fatal(err) } if ft.Claims["admin"] != true { t.Errorf("Claims['admin'] = %v; want = true", ft.Claims["admin"]) @@ -223,6 +229,7 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { t.Errorf("UID = %q; Sub = %q; want UID = Sub", ft.UID, ft.Subject) } } + func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() From 514d0a37a1ef87b39187d9b5ab204656bb186af3 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 29 Jan 2018 14:57:53 -0500 Subject: [PATCH 18/31] changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee0af8b..471cd088 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Unreleased - +### Token revokaction +- [added] The ['VerifyIDToken(...)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) + method can now take an additional parameter `check_revoked`, when `True`, + a further check will be performed to see if the token has been revoked. +- [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) + has been added to invalidate all tokens issued before the current second. +- [added] A new property `TokensValidAfterTime` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) + # v2.4.0 ### Initialization From d4fb73dbf805d49c9eb858da70edab3b322e1486 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 30 Jan 2018 10:02:21 -0500 Subject: [PATCH 19/31] tokensValidAfter in millies ; do not fail on existing users in tests. --- auth/auth.go | 12 +++---- auth/auth_test.go | 14 +++++--- auth/user_mgt.go | 14 +++++--- auth/user_mgt_test.go | 12 +++---- integration/auth/auth_test.go | 27 ++++++++++------ integration/auth/user_mgt_test.go | 54 ++++++++++++++++++++----------- 6 files changed, 84 insertions(+), 49 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 23ccd842..2ff784ee 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,15 +177,15 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } -// RevokeRefreshToken revokes all refresh tokens for the specified user identified by the uid provided. +// RevokeRefreshTokens revokes all refresh tokens for the specified user identified by the uid provided. // In addition to revoking all refresh tokens for a user, all ID tokens issued // before revocation will also be revoked on the Auth backend. Any request with an // ID token generated before revocation will be rejected with a token expired error. // Note that due to the fact that the timestamp is stored in seconds, any tokens minted in // the same second as the revocation will still be valid. If there is a chance that a token -// was minted in the last second, delay for 1 second before revoking.all tokens minted before the current second. -func (c *Client) RevokeRefreshToken(ctx context.Context, uid string) error { - return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshToken()) +// was minted in the last second, delay for 1 second before revoking.all tokens minted before the current second. +func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { + return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshTokens()) } // VerifyIDToken verifies the signature and payload of the provided ID token. @@ -248,7 +248,7 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) { return p, nil } -// VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and +// VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and // if requested, whether it was revoked. // see: VerifyIDToken above. func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string, checkToken bool) (*Token, error) { @@ -261,7 +261,7 @@ func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken stri if err != nil { return p, err } - if p.IssuedAt < user.TokensValidAfterTime { + if p.IssuedAt*1000 < user.TokensValidAfterTime { err = fmt.Errorf("the Firebase ID token has been revoked") } return p, err diff --git a/auth/auth_test.go b/auth/auth_test.go index 090fdc68..5e3fa92e 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -216,8 +216,11 @@ func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { t.Fatal("Error retrieving user") } - ft, err := s.Client.VerifyIDTokenWithCheckRevoked(nil, - getIDToken(mockIDTokenPayload{"uid": "uid", "iat": int(u.TokensValidAfterTime - 10)}), + ft, err := s.Client.VerifyIDTokenWithCheckRevoked( + nil, + getIDToken(mockIDTokenPayload{ + "uid": "uid", + "iat": int(u.TokensValidAfterTime/1000) - 10}), false) if err != nil { t.Fatal(err) @@ -237,8 +240,11 @@ func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { if err != nil { t.Fatal("Error retrieving user") } - _, err = s.Client.VerifyIDTokenWithCheckRevoked(ctx, - getIDToken(mockIDTokenPayload{"uid": "uid", "iat": int(u.TokensValidAfterTime - 10)}), + _, err = s.Client.VerifyIDTokenWithCheckRevoked( + ctx, + getIDToken(mockIDTokenPayload{ + "uid": "uid", + "iat": int(u.TokensValidAfterTime/1000) - 10}), true) we := "the Firebase ID token has been revoked" if err == nil || err.Error() != we { diff --git a/auth/user_mgt.go b/auth/user_mgt.go index d2645379..89a38815 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -67,6 +67,7 @@ type UserInfo struct { } // UserMetadata contains additional metadata associated with a user account. +// Timestamps are in epoch milliseconds. type UserMetadata struct { CreationTimestamp int64 LastLogInTimestamp int64 @@ -79,7 +80,7 @@ type UserRecord struct { Disabled bool EmailVerified bool ProviderUserInfo []*UserInfo - TokensValidAfterTime int64 + TokensValidAfterTime int64 // Epoch milliseconds UserMetadata *UserMetadata } @@ -176,7 +177,9 @@ func (u *UserToUpdate) PhoneNumber(phone string) *UserToUpdate { u.set("phoneNum // PhotoURL setter. func (u *UserToUpdate) PhotoURL(url string) *UserToUpdate { u.set("photoUrl", url); return u } -func (u *UserToUpdate) revokeRefreshToken() *UserToUpdate { +// revokeRefreshTokens revokes all refresh tokens for a user by setting the validSince property +// to the present in epoch seconds. +func (u *UserToUpdate) revokeRefreshTokens() *UserToUpdate { u.set("validSince", time.Now().Unix()) return u } @@ -425,8 +428,9 @@ func validatePhone(val interface{}) error { } func validateValidSince(val interface{}) error { - if _, ok := val.(int64); !ok { - return fmt.Errorf("tokens valid after time must be an integer") + if v, ok := val.(int64); !ok || v > ((time.Now().Unix())*999) { + // If v is in seconds it signifies a date 40K years in the future, most likely milliseconds. + return fmt.Errorf("validSince must be an integer signifying epoch seconds") } return nil } @@ -620,7 +624,7 @@ func makeExportedUser(r *identitytoolkit.UserInfo) (*ExportedUserRecord, error) Disabled: r.Disabled, EmailVerified: r.EmailVerified, ProviderUserInfo: providerUserInfo, - TokensValidAfterTime: r.ValidSince, + TokensValidAfterTime: r.ValidSince * 1000, UserMetadata: &UserMetadata{ LastLogInTimestamp: r.LastLoginAt, CreationTimestamp: r.CreatedAt, diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 73393601..436e1b53 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -60,7 +60,7 @@ var testUser = &UserRecord{ UID: "testuid", }, }, - TokensValidAfterTime: 1494364393, + TokensValidAfterTime: 1494364393000, UserMetadata: &UserMetadata{ CreationTimestamp: 1234567890, LastLogInTimestamp: 1233211232, @@ -498,7 +498,7 @@ func TestUpdateUser(t *testing.T) { } } } -func TestRevokeRefreshToken(t *testing.T) { +func TestRevokeRefreshTokens(t *testing.T) { resp := `{ "kind": "identitytoolkit#SetAccountInfoResponse", "localId": "expectedUserID" @@ -506,7 +506,7 @@ func TestRevokeRefreshToken(t *testing.T) { s := echoServer([]byte(resp), t) defer s.Close() before := time.Now().Unix() - err := s.Client.RevokeRefreshToken(nil, "some_uid") + err := s.Client.RevokeRefreshTokens(nil, "some_uid") if err != nil { t.Error(err) } @@ -522,7 +522,7 @@ func TestRevokeRefreshToken(t *testing.T) { } } -func TestRevokeRefreshTokenInvalidUID(t *testing.T) { +func TestRevokeRefreshTokensInvalidUID(t *testing.T) { resp := `{ "kind": "identitytoolkit#SetAccountInfoResponse", "localId": "expectedUserID" @@ -531,8 +531,8 @@ func TestRevokeRefreshTokenInvalidUID(t *testing.T) { defer s.Close() we := "uid must not be empty" - if err := s.Client.RevokeRefreshToken(nil, ""); err.Error() != we { - t.Errorf("RevokeRefreshToken(); err = %s; want err = %s", err.Error(), we) + if err := s.Client.RevokeRefreshTokens(nil, ""); err.Error() != we { + t.Errorf("RevokeRefreshTokens(); err = %s; want err = %s", err.Error(), we) } } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 25b31772..ebe81dea 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -63,7 +63,6 @@ func TestCustomToken(t *testing.T) { if err != nil { t.Fatal(err) } - idt, err := signInWithCustomToken(ct) if err != nil { t.Fatal(err) @@ -76,6 +75,9 @@ func TestCustomToken(t *testing.T) { if vt.UID != "user1" { t.Errorf("UID = %q; want UID = %q", vt.UID, "user1") } + if err = client.DeleteUser(context.Background(), "user1"); err != nil { + t.Error(err) + } } func TestCustomTokenVerifyCheckRevoked(t *testing.T) { @@ -85,7 +87,6 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { if err != nil { t.Fatal(err) } - idt, err := signInWithCustomToken(ct) if err != nil { t.Fatal(err) @@ -99,7 +100,7 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { t.Errorf("UID = %q; want UID = %q", vt.UID, revokedID) } time.Sleep(time.Second) - if err = client.RevokeRefreshToken(ctx, revokedID); err != nil { + if err = client.RevokeRefreshTokens(ctx, revokedID); err != nil { t.Fatal(err) } @@ -114,10 +115,6 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { if err != nil { t.Errorf("VerifyIDTokenWithCheckRevoked(.., false); err = %s; want err = ", err) } - err = client.DeleteUser(ctx, revokedID) - if err != nil { - t.Error(err) - } // Sign in after revocation after revocation. if idt, err = signInWithCustomToken(ct); err != nil { @@ -127,10 +124,15 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { if _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, true); err != nil { t.Errorf("VerifyIDTokenWithCheckRevoked(); err = %s; want err = ", err) } + + err = client.DeleteUser(ctx, revokedID) + if err != nil { + t.Error(err) + } } func TestCustomTokenWithClaims(t *testing.T) { - ct, err := client.CustomTokenWithClaims("user1", map[string]interface{}{ + ct, err := client.CustomTokenWithClaims("user2", map[string]interface{}{ "premium": true, "package": "gold", }) @@ -147,8 +149,8 @@ func TestCustomTokenWithClaims(t *testing.T) { if err != nil { t.Fatal(err) } - if vt.UID != "user1" { - t.Errorf("UID = %q; want UID = %q", vt.UID, "user1") + if vt.UID != "user2" { + t.Errorf("UID = %q; want UID = %q", vt.UID, "user2") } if premium, ok := vt.Claims["premium"].(bool); !ok || !premium { t.Errorf("Claims['premium'] = %v; want Claims['premium'] = true", vt.Claims["premium"]) @@ -156,6 +158,11 @@ func TestCustomTokenWithClaims(t *testing.T) { if pkg, ok := vt.Claims["package"].(string); !ok || pkg != "gold" { t.Errorf("Claims['package'] = %v; want Claims['package'] = \"gold\"", vt.Claims["package"]) } + + if err = client.DeleteUser(context.Background(), "user2"); err != nil { + t.Error(err) + } + } func signInWithCustomToken(token string) (string, error) { diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index 3a0cc2e1..75e38791 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -18,6 +18,7 @@ package auth import ( "fmt" "reflect" + "strings" "testing" "time" @@ -35,16 +36,26 @@ var testFixtures = struct { }{} func TestUserManagement(t *testing.T) { - t.Run("Create test users", testCreateUsers) - t.Run("Get user", testGetUser) - t.Run("Iterate users", testUserIterator) - t.Run("Paged iteration", testPager) - t.Run("Disable user account", testDisableUser) - t.Run("Update user", testUpdateUser) - t.Run("Remove user attributes", testRemovePhonePhotoName) - t.Run("Remove custom claims", testRemoveCustomClaims) - t.Run("Add custom claims", testAddCustomClaims) - t.Run("Delete test users", testDeleteUsers) + orderedRuns := []struct { + name string + testFunc func(*testing.T) + }{ + {"Create test users", testCreateUsers}, + {"Get user", testGetUser}, + {"Iterate users", testUserIterator}, + {"Paged iteration", testPager}, + {"Disable user account", testDisableUser}, + {"Update user", testUpdateUser}, + {"Remove user attributes", testRemovePhonePhotoName}, + {"Remove custom claims", testRemoveCustomClaims}, + {"Add custom claims", testAddCustomClaims}, + {"Delete test users", testDeleteUsers}, + } + for _, run := range orderedRuns { + if ok := t.Run(run.name, run.testFunc); !ok { + t.Fatalf("Failed run %v", run.name) + } + } } // N.B if the tests are failing due to inability to create existing users, manual @@ -53,27 +64,33 @@ func TestUserManagement(t *testing.T) { func testCreateUsers(t *testing.T) { // Create users with uid for i := 0; i < 3; i++ { - params := (&auth.UserToCreate{}).UID(fmt.Sprintf("tempTestUserID-%d", i)) + uid := fmt.Sprintf("tempTestUserID-%d", i) + params := (&auth.UserToCreate{}).UID(uid) u, err := client.CreateUser(context.Background(), params) if err != nil { - t.Fatal("failed to create user", i, err) + if strings.Contains(err.Error(), "DUPLICATE_LOCAL_ID") { + u, err = client.GetUser(context.Background(), uid) + if err != nil { + t.Fatal(err) + } + } else { + t.Fatal(err) + } } testFixtures.uidList = append(testFixtures.uidList, u.UID) } - // Create user with no parameters (zero-value) u, err := client.CreateUser(context.Background(), (&auth.UserToCreate{})) if err != nil { t.Fatal(err) } - // make sure that the user.TokensValidAvterTime is not in the future or stale. - if u.TokensValidAfterTime > time.Now().Unix() { + // make sure that the user.TokensValidAfterTime is not in the future or stale. + if u.TokensValidAfterTime > time.Now().Unix()*1000 { t.Errorf("timestamp cannot be in the future") } - if u.TokensValidAfterTime < time.Now().Unix()-3600 { + if u.TokensValidAfterTime < (time.Now().Unix()-3600)*1000 { t.Errorf("timestamp cannot be old") } - testFixtures.sampleUserBlank = u testFixtures.uidList = append(testFixtures.uidList, u.UID) @@ -85,7 +102,7 @@ func testCreateUsers(t *testing.T) { DisplayName("display_name"). Password("password") u, err = client.CreateUser(context.Background(), params) - if err != nil { + if err != nil || u == nil { t.Fatal(err) } testFixtures.sampleUserWithData = u @@ -94,6 +111,7 @@ func testCreateUsers(t *testing.T) { func testGetUser(t *testing.T) { want := testFixtures.sampleUserWithData + u, err := client.GetUser(context.Background(), want.UID) if err != nil { t.Fatalf("error getting user %s", err) From 7cd2eaf2be4b342e322d609a8ebce0c7dbdb826e Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Wed, 31 Jan 2018 15:20:10 -0500 Subject: [PATCH 20/31] addressing PR comments --- CHANGELOG.md | 6 +++--- auth/auth.go | 16 ++++++++-------- auth/auth_test.go | 9 +++++---- auth/user_mgt.go | 2 +- integration/auth/auth_test.go | 21 +++++++++++---------- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 471cd088..c53eabe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ - ### Token revokaction -- [added] The ['VerifyIDToken(...)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) - method can now take an additional parameter `check_revoked`, when `True`, - a further check will be performed to see if the token has been revoked. +- [added] The ['VerifyIDTokenWithCheckRevoked(ctx, token, checkRevoked)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) + method accepts a boolean `checkRevoked` signifying whether to check + if the token has been revoked. - [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) has been added to invalidate all tokens issued before the current second. - [added] A new property `TokensValidAfterTime` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) diff --git a/auth/auth.go b/auth/auth.go index 2ff784ee..d105a340 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,13 +177,12 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } -// RevokeRefreshTokens revokes all refresh tokens for the specified user identified by the uid provided. +// RevokeRefreshTokens revokes all refresh tokens for the specified user. // In addition to revoking all refresh tokens for a user, all ID tokens issued -// before revocation will also be revoked on the Auth backend. Any request with an +// before revocation will also be revoked at the Auth backend. Any request with an // ID token generated before revocation will be rejected with a token expired error. -// Note that due to the fact that the timestamp is stored in seconds, any tokens minted in -// the same second as the revocation will still be valid. If there is a chance that a token -// was minted in the last second, delay for 1 second before revoking.all tokens minted before the current second. +// Note that any tokens minted in the same epoch second as the revocation will still be valid. +// If there is a chance that a token was minted in the last second, delay for 1 second before revoking. func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshTokens()) } @@ -251,10 +250,10 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) { // VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and // if requested, whether it was revoked. // see: VerifyIDToken above. -func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string, checkToken bool) (*Token, error) { +func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string, checkRevoked bool) (*Token, error) { p, err := c.VerifyIDToken(idToken) - if !checkToken || err != nil { + if !checkRevoked || err != nil { return p, err } user, err := c.GetUser(ctx, p.UID) @@ -262,7 +261,8 @@ func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken stri return p, err } if p.IssuedAt*1000 < user.TokensValidAfterTime { - err = fmt.Errorf("the Firebase ID token has been revoked") + err = fmt.Errorf("id token has been revoked") + p = nil } return p, err } diff --git a/auth/auth_test.go b/auth/auth_test.go index 5e3fa92e..f3720581 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -240,15 +240,16 @@ func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { if err != nil { t.Fatal("Error retrieving user") } - _, err = s.Client.VerifyIDTokenWithCheckRevoked( + p, err := s.Client.VerifyIDTokenWithCheckRevoked( ctx, getIDToken(mockIDTokenPayload{ "uid": "uid", "iat": int(u.TokensValidAfterTime/1000) - 10}), true) - we := "the Firebase ID token has been revoked" - if err == nil || err.Error() != we { - t.Errorf("VerifyIDTokenWithCheckRevoked(..., token, true) = %v; want = %v", err, we) + we := "id token has been revoked" + if p != nil || err == nil || err.Error() != we { + t.Errorf("VerifyIDTokenWithCheckRevoked(..., token, true) =(%v, %v); want = (%v, %v)", + p, err, nil, we) } } diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 89a38815..0f04132a 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -428,7 +428,7 @@ func validatePhone(val interface{}) error { } func validateValidSince(val interface{}) error { - if v, ok := val.(int64); !ok || v > ((time.Now().Unix())*999) { + if v, ok := val.(int64); !ok || v > 1e12 { // If v is in seconds it signifies a date 40K years in the future, most likely milliseconds. return fmt.Errorf("validSince must be an integer signifying epoch seconds") } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index ebe81dea..b65c447c 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -81,8 +81,8 @@ func TestCustomToken(t *testing.T) { } func TestCustomTokenVerifyCheckRevoked(t *testing.T) { - revokedID := "user_revoked" - ct, err := client.CustomToken(revokedID) + uid := "user_revoked" + ct, err := client.CustomToken(uid) if err != nil { t.Fatal(err) @@ -96,18 +96,19 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { if err != nil { t.Fatal(err) } - if vt.UID != revokedID { - t.Errorf("UID = %q; want UID = %q", vt.UID, revokedID) + if vt.UID != uid { + t.Errorf("UID = %q; want UID = %q", vt.UID, uid) } time.Sleep(time.Second) - if err = client.RevokeRefreshTokens(ctx, revokedID); err != nil { + if err = client.RevokeRefreshTokens(ctx, uid); err != nil { t.Fatal(err) } vt, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, true) - we := "the Firebase ID token has been revoked" - if err == nil || err.Error() != we { - t.Errorf("VerifyIDTokenWithCheckRevoked; err = %s; want err = %v", err, we) + we := "id token has been revoked" + if vt != nil || err == nil || err.Error() != we { + t.Errorf("tok, err := VerifyIDTokenWithCheckRevoked(); got (%v, %s) ; want (%v, %v)", + vt, err, nil, we) } // Does not return error if it isn't checked @@ -116,7 +117,7 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { t.Errorf("VerifyIDTokenWithCheckRevoked(.., false); err = %s; want err = ", err) } - // Sign in after revocation after revocation. + // Sign in after revocation. if idt, err = signInWithCustomToken(ct); err != nil { t.Fatal(err) } @@ -125,7 +126,7 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { t.Errorf("VerifyIDTokenWithCheckRevoked(); err = %s; want err = ", err) } - err = client.DeleteUser(ctx, revokedID) + err = client.DeleteUser(ctx, uid) if err != nil { t.Error(err) } From 4e09f44ea3f82b6da0426fc82307f5de5edd663a Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 1 Feb 2018 14:28:10 -0500 Subject: [PATCH 21/31] remove boolean checkRevoked from VerifyIDTokenWithCheckRevoked --- CHANGELOG.md | 10 ++++------ auth/auth.go | 17 +++++++++-------- auth/auth_test.go | 14 ++++++-------- integration/auth/auth_test.go | 10 +++++----- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c53eabe7..cd92123b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,10 @@ - ### Token revokaction -- [added] The ['VerifyIDTokenWithCheckRevoked(ctx, token, checkRevoked)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) - method accepts a boolean `checkRevoked` signifying whether to check - if the token has been revoked. -- [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) - has been added to invalidate all tokens issued before the current second. -- [added] A new property `TokensValidAfterTime` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) +- [added] A New ['VerifyIDTokenWithCheckRevoked(ctx, token)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) + method checks to see if the token has been revoked. A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) + has been added to invalidate all tokens issued before the current second. [added] A new property + `TokensValidAfterTime` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) # v2.4.0 diff --git a/auth/auth.go b/auth/auth.go index d105a340..a96f0aa4 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -248,23 +248,24 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) { } // VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and -// if requested, whether it was revoked. +// checks that it wasn't revoked. // see: VerifyIDToken above. -func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string, checkRevoked bool) (*Token, error) { +func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string) (*Token, error) { p, err := c.VerifyIDToken(idToken) - if !checkRevoked || err != nil { - return p, err + if err != nil { + return nil, err } + user, err := c.GetUser(ctx, p.UID) if err != nil { - return p, err + return nil, err } + if p.IssuedAt*1000 < user.TokensValidAfterTime { - err = fmt.Errorf("id token has been revoked") - p = nil + return nil, fmt.Errorf("id token has been revoked") } - return p, err + return p, nil } func parseKey(key string) (*rsa.PrivateKey, error) { diff --git a/auth/auth_test.go b/auth/auth_test.go index f3720581..46d18811 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -196,7 +196,7 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() - ft, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, testIDToken, true) + ft, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, testIDToken) if err != nil { t.Error(err) } @@ -216,12 +216,10 @@ func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { t.Fatal("Error retrieving user") } - ft, err := s.Client.VerifyIDTokenWithCheckRevoked( - nil, + ft, err := s.Client.VerifyIDToken( getIDToken(mockIDTokenPayload{ "uid": "uid", - "iat": int(u.TokensValidAfterTime/1000) - 10}), - false) + "iat": int(u.TokensValidAfterTime/1000) - 10})) if err != nil { t.Fatal(err) } @@ -244,11 +242,11 @@ func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { ctx, getIDToken(mockIDTokenPayload{ "uid": "uid", - "iat": int(u.TokensValidAfterTime/1000) - 10}), - true) + "iat": int(u.TokensValidAfterTime/1000) - 10, + })) we := "id token has been revoked" if p != nil || err == nil || err.Error() != we { - t.Errorf("VerifyIDTokenWithCheckRevoked(..., token, true) =(%v, %v); want = (%v, %v)", + t.Errorf("VerifyIDTokenWithCheckRevoked(..., token) =(%v, %v); want = (%v, %v)", p, err, nil, we) } } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index b65c447c..cb3b67ac 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -92,7 +92,7 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { t.Fatal(err) } ctx := context.Background() - vt, err := client.VerifyIDTokenWithCheckRevoked(ctx, idt, true) + vt, err := client.VerifyIDTokenWithCheckRevoked(ctx, idt) if err != nil { t.Fatal(err) } @@ -104,7 +104,7 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { t.Fatal(err) } - vt, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, true) + vt, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt) we := "id token has been revoked" if vt != nil || err == nil || err.Error() != we { t.Errorf("tok, err := VerifyIDTokenWithCheckRevoked(); got (%v, %s) ; want (%v, %v)", @@ -112,9 +112,9 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { } // Does not return error if it isn't checked - _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, false) + _, err = client.VerifyIDToken(idt) if err != nil { - t.Errorf("VerifyIDTokenWithCheckRevoked(.., false); err = %s; want err = ", err) + t.Errorf("VerifyIDToken(); err = %s; want err = ", err) } // Sign in after revocation. @@ -122,7 +122,7 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { t.Fatal(err) } - if _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt, true); err != nil { + if _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt); err != nil { t.Errorf("VerifyIDTokenWithCheckRevoked(); err = %s; want err = ", err) } From b18dab2784c8cf5a8e8bf5664017a3cb3169af19 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Thu, 1 Feb 2018 18:48:17 -0500 Subject: [PATCH 22/31] PR comment fixes --- CHANGELOG.md | 7 ++++--- auth/auth.go | 11 ++++++----- auth/auth_test.go | 25 ++++++------------------- auth/user_mgt.go | 4 ++-- auth/user_mgt_test.go | 8 +++----- integration/auth/auth_test.go | 13 ++++++++----- integration/auth/user_mgt_test.go | 5 +++++ 7 files changed, 34 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd92123b..3e53e46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ # Unreleased -- ### Token revokaction - [added] A New ['VerifyIDTokenWithCheckRevoked(ctx, token)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) - method checks to see if the token has been revoked. A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) - has been added to invalidate all tokens issued before the current second. [added] A new property + method checks to see if the token has been revoked. +- [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) + has been added to invalidate all tokens issued before the current second. +- [added] A new property `TokensValidAfterTime` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) # v2.4.0 diff --git a/auth/auth.go b/auth/auth.go index a96f0aa4..a0c34510 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,12 +177,12 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } +// RevokeRefreshTokens revokes all refresh tokens for the specified user. +// // RevokeRefreshTokens revokes all refresh tokens for the specified user. // In addition to revoking all refresh tokens for a user, all ID tokens issued // before revocation will also be revoked at the Auth backend. Any request with an // ID token generated before revocation will be rejected with a token expired error. -// Note that any tokens minted in the same epoch second as the revocation will still be valid. -// If there is a chance that a token was minted in the last second, delay for 1 second before revoking. func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshTokens()) } @@ -194,6 +194,7 @@ func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { // a Token containing the decoded claims in the input JWT. See // https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for // more details on how to obtain an ID token in a client app. +// This does not check whether or not the token has been revoked, see VerifyIDTokenWithCheckRevoked below. func (c *Client) VerifyIDToken(idToken string) (*Token, error) { if c.projectID == "" { return nil, errors.New("project id not available") @@ -247,12 +248,12 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) { return p, nil } +// VerifyIDTokenWithCheckRevoked verifies the provided ID token and checks it has not been revoked. +// // VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and -// checks that it wasn't revoked. -// see: VerifyIDToken above. +// checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT. func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string) (*Token, error) { p, err := c.VerifyIDToken(idToken) - if err != nil { return nil, err } diff --git a/auth/auth_test.go b/auth/auth_test.go index 46d18811..417a3212 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -211,15 +211,9 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() - u, err := s.Client.GetUser(ctx, "testuser") - if err != nil { - t.Fatal("Error retrieving user") - } + tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token - ft, err := s.Client.VerifyIDToken( - getIDToken(mockIDTokenPayload{ - "uid": "uid", - "iat": int(u.TokensValidAfterTime/1000) - 10})) + ft, err := s.Client.VerifyIDToken(tok) if err != nil { t.Fatal(err) } @@ -234,19 +228,12 @@ func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() - u, err := s.Client.GetUser(ctx, "testuser") - if err != nil { - t.Fatal("Error retrieving user") - } - p, err := s.Client.VerifyIDTokenWithCheckRevoked( - ctx, - getIDToken(mockIDTokenPayload{ - "uid": "uid", - "iat": int(u.TokensValidAfterTime/1000) - 10, - })) + tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token + + p, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, tok) we := "id token has been revoked" if p != nil || err == nil || err.Error() != we { - t.Errorf("VerifyIDTokenWithCheckRevoked(..., token) =(%v, %v); want = (%v, %v)", + t.Errorf("VerifyIDTokenWithCheckRevoked(ctx, token) =(%v, %v); want = (%v, %v)", p, err, nil, we) } } diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 0f04132a..8fd3da00 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -67,7 +67,7 @@ type UserInfo struct { } // UserMetadata contains additional metadata associated with a user account. -// Timestamps are in epoch milliseconds. +// Timestamps are in milliseconds since epoch. type UserMetadata struct { CreationTimestamp int64 LastLogInTimestamp int64 @@ -80,7 +80,7 @@ type UserRecord struct { Disabled bool EmailVerified bool ProviderUserInfo []*UserInfo - TokensValidAfterTime int64 // Epoch milliseconds + TokensValidAfterTime int64 // milliseconds since epoch. UserMetadata *UserMetadata } diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 436e1b53..0020541a 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -506,15 +506,13 @@ func TestRevokeRefreshTokens(t *testing.T) { s := echoServer([]byte(resp), t) defer s.Close() before := time.Now().Unix() - err := s.Client.RevokeRefreshTokens(nil, "some_uid") - if err != nil { + if err := s.Client.RevokeRefreshTokens(nil, "some_uid"); err != nil { t.Error(err) } after := time.Now().Unix() req := &identitytoolkit.IdentitytoolkitRelyingpartySetAccountInfoRequest{} - err = json.Unmarshal(s.Rbody, &req) - if err != nil { + if err := json.Unmarshal(s.Rbody, &req); err != nil { t.Error(err) } if req.ValidSince > after || req.ValidSince < before { @@ -531,7 +529,7 @@ func TestRevokeRefreshTokensInvalidUID(t *testing.T) { defer s.Close() we := "uid must not be empty" - if err := s.Client.RevokeRefreshTokens(nil, ""); err.Error() != we { + if err := s.Client.RevokeRefreshTokens(nil, ""); err == nil || err.Error() != we { t.Errorf("RevokeRefreshTokens(); err = %s; want err = %s", err.Error(), we) } } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index cb3b67ac..6d0d3db1 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -80,7 +80,7 @@ func TestCustomToken(t *testing.T) { } } -func TestCustomTokenVerifyCheckRevoked(t *testing.T) { +func TestVerifyIDTokenWithCheckRevoked(t *testing.T) { uid := "user_revoked" ct, err := client.CustomToken(uid) @@ -99,6 +99,11 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { if vt.UID != uid { t.Errorf("UID = %q; want UID = %q", vt.UID, uid) } + // The backend stores the validSince property in seconds since the epoch. + // The issuedAt property of the token is also in seconds, if a token was + // issued, and then in the same second tokens were revoked, the token will + // have the same timestamp as the tokensValidAfterTime, and will therefore + // not be considered revoked. Hence we wait one second before revoking. time.Sleep(time.Second) if err = client.RevokeRefreshTokens(ctx, uid); err != nil { t.Fatal(err) @@ -111,9 +116,8 @@ func TestCustomTokenVerifyCheckRevoked(t *testing.T) { vt, err, nil, we) } - // Does not return error if it isn't checked - _, err = client.VerifyIDToken(idt) - if err != nil { + // Does not return error for revoked token. + if _, err = client.VerifyIDToken(idt); err != nil { t.Errorf("VerifyIDToken(); err = %s; want err = ", err) } @@ -159,7 +163,6 @@ func TestCustomTokenWithClaims(t *testing.T) { if pkg, ok := vt.Claims["package"].(string); !ok || pkg != "gold" { t.Errorf("Claims['package'] = %v; want Claims['package'] = \"gold\"", vt.Claims["package"]) } - if err = client.DeleteUser(context.Background(), "user2"); err != nil { t.Error(err) } diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index 75e38791..e95fd0d1 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -51,6 +51,10 @@ func TestUserManagement(t *testing.T) { {"Add custom claims", testAddCustomClaims}, {"Delete test users", testDeleteUsers}, } + // The tests are meant to be run in sequence, a failure in creating the users + // should be fatal so non of the other tests run. However calling Fatal from a + // subtest does not prevent the other subtests from running, hence we check the + // success of each subtest before proceeding. for _, run := range orderedRuns { if ok := t.Run(run.name, run.testFunc); !ok { t.Fatalf("Failed run %v", run.name) @@ -88,6 +92,7 @@ func testCreateUsers(t *testing.T) { if u.TokensValidAfterTime > time.Now().Unix()*1000 { t.Errorf("timestamp cannot be in the future") } + log.Printf("\n -- - - - \n%#v, %v \n..,,,,..\n", u, u.TokensValidAfterTime) if u.TokensValidAfterTime < (time.Now().Unix()-3600)*1000 { t.Errorf("timestamp cannot be old") } From 1c0d9d663abf08e87396020395bb449757ea0d82 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 2 Feb 2018 00:59:46 -0500 Subject: [PATCH 23/31] PR comment fixes --- auth/user_mgt.go | 5 ++-- auth/user_mgt_test.go | 4 +-- integration/auth/auth_test.go | 1 - integration/auth/user_mgt_test.go | 44 ++++++++++++++++++------------- 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 8fd3da00..bcc1701d 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -428,9 +428,8 @@ func validatePhone(val interface{}) error { } func validateValidSince(val interface{}) error { - if v, ok := val.(int64); !ok || v > 1e12 { - // If v is in seconds it signifies a date 40K years in the future, most likely milliseconds. - return fmt.Errorf("validSince must be an integer signifying epoch seconds") + if _, ok := val.(int64); !ok { + return fmt.Errorf("validSince must be an integer signifying seconds since the epoch") } return nil } diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 0020541a..95f94345 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -506,7 +506,7 @@ func TestRevokeRefreshTokens(t *testing.T) { s := echoServer([]byte(resp), t) defer s.Close() before := time.Now().Unix() - if err := s.Client.RevokeRefreshTokens(nil, "some_uid"); err != nil { + if err := s.Client.RevokeRefreshTokens(context.Background(), "some_uid"); err != nil { t.Error(err) } after := time.Now().Unix() @@ -529,7 +529,7 @@ func TestRevokeRefreshTokensInvalidUID(t *testing.T) { defer s.Close() we := "uid must not be empty" - if err := s.Client.RevokeRefreshTokens(nil, ""); err == nil || err.Error() != we { + if err := s.Client.RevokeRefreshTokens(context.Background(), ""); err == nil || err.Error() != we { t.Errorf("RevokeRefreshTokens(); err = %s; want err = %s", err.Error(), we) } } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 6d0d3db1..980999ef 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -166,7 +166,6 @@ func TestCustomTokenWithClaims(t *testing.T) { if err = client.DeleteUser(context.Background(), "user2"); err != nil { t.Error(err) } - } func signInWithCustomToken(token string) (string, error) { diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index e95fd0d1..f6d81375 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -62,6 +62,20 @@ func TestUserManagement(t *testing.T) { } } +func forceCreateUser(uid string, params *auth.UserToCreate) (*auth.UserRecord, error) { + u, err := client.CreateUser(context.Background(), params) + if err != nil { + if strings.Contains(err.Error(), "DUPLICATE_LOCAL_ID") { + if err = client.DeleteUser(context.Background(), uid); err != nil { + return nil, err + } + return client.CreateUser(context.Background(), params) + } + return nil, err + } + return u, nil +} + // N.B if the tests are failing due to inability to create existing users, manual // cleanup of the previus test run might be required, delete the unwanted users via: // https://console.firebase.google.com/u/0/project//authentication/users @@ -70,32 +84,25 @@ func testCreateUsers(t *testing.T) { for i := 0; i < 3; i++ { uid := fmt.Sprintf("tempTestUserID-%d", i) params := (&auth.UserToCreate{}).UID(uid) - u, err := client.CreateUser(context.Background(), params) + u, err := forceCreateUser(uid, params) if err != nil { - if strings.Contains(err.Error(), "DUPLICATE_LOCAL_ID") { - u, err = client.GetUser(context.Background(), uid) - if err != nil { - t.Fatal(err) - } - } else { - t.Fatal(err) - } + t.Fatal(err) } testFixtures.uidList = append(testFixtures.uidList, u.UID) + // make sure that the user.TokensValidAfterTime is not in the future or stale. + if u.TokensValidAfterTime > time.Now().Unix()*1000 { + t.Errorf("timestamp cannot be in the future") + } + if time.Now().Sub(time.Unix(u.TokensValidAfterTime, 0)) > time.Hour { + t.Errorf("timestamp should be recent") + } + } // Create user with no parameters (zero-value) u, err := client.CreateUser(context.Background(), (&auth.UserToCreate{})) if err != nil { t.Fatal(err) } - // make sure that the user.TokensValidAfterTime is not in the future or stale. - if u.TokensValidAfterTime > time.Now().Unix()*1000 { - t.Errorf("timestamp cannot be in the future") - } - log.Printf("\n -- - - - \n%#v, %v \n..,,,,..\n", u, u.TokensValidAfterTime) - if u.TokensValidAfterTime < (time.Now().Unix()-3600)*1000 { - t.Errorf("timestamp cannot be old") - } testFixtures.sampleUserBlank = u testFixtures.uidList = append(testFixtures.uidList, u.UID) @@ -106,8 +113,7 @@ func testCreateUsers(t *testing.T) { Email(uid + "email@test.com"). DisplayName("display_name"). Password("password") - u, err = client.CreateUser(context.Background(), params) - if err != nil || u == nil { + if u, err = forceCreateUser(uid, params); err != nil { t.Fatal(err) } testFixtures.sampleUserWithData = u From 19fbeb0444ba34da16075328c457570029294e19 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 2 Feb 2018 15:32:53 -0500 Subject: [PATCH 24/31] s/Time/Millis/; s/WithCheck/AndCheck/ --- CHANGELOG.md | 4 ++-- auth/auth.go | 10 +++++----- auth/auth_test.go | 12 ++++++------ auth/user_mgt.go | 22 +++++++++++----------- auth/user_mgt_test.go | 2 +- integration/auth/auth_test.go | 14 +++++++------- integration/auth/user_mgt_test.go | 10 +++++----- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e53e46e..0bcce323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # Unreleased ### Token revokaction -- [added] A New ['VerifyIDTokenWithCheckRevoked(ctx, token)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) +- [added] A New ['VerifyIDTokenAndCheckRevoked(ctx, token)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) method checks to see if the token has been revoked. - [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) has been added to invalidate all tokens issued before the current second. - [added] A new property - `TokensValidAfterTime` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) + `TokensValidAfterMillis` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) # v2.4.0 diff --git a/auth/auth.go b/auth/auth.go index a0c34510..71eee91d 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -194,7 +194,7 @@ func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { // a Token containing the decoded claims in the input JWT. See // https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for // more details on how to obtain an ID token in a client app. -// This does not check whether or not the token has been revoked, see VerifyIDTokenWithCheckRevoked below. +// This does not check whether or not the token has been revoked, see VerifyIDTokenAndCheckRevoked below. func (c *Client) VerifyIDToken(idToken string) (*Token, error) { if c.projectID == "" { return nil, errors.New("project id not available") @@ -248,11 +248,11 @@ func (c *Client) VerifyIDToken(idToken string) (*Token, error) { return p, nil } -// VerifyIDTokenWithCheckRevoked verifies the provided ID token and checks it has not been revoked. +// VerifyIDTokenAndCheckRevoked verifies the provided ID token and checks it has not been revoked. // -// VerifyIDTokenWithCheckRevoked verifies the signature and payload of the provided ID token and +// VerifyIDTokenAndCheckRevoked verifies the signature and payload of the provided ID token and // checks that it wasn't revoked. Uses VerifyIDToken() internally to verify the ID token JWT. -func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken string) (*Token, error) { +func (c *Client) VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken string) (*Token, error) { p, err := c.VerifyIDToken(idToken) if err != nil { return nil, err @@ -263,7 +263,7 @@ func (c *Client) VerifyIDTokenWithCheckRevoked(ctx context.Context, idToken stri return nil, err } - if p.IssuedAt*1000 < user.TokensValidAfterTime { + if p.IssuedAt*1000 < user.TokensValidAfterMillis { return nil, fmt.Errorf("id token has been revoked") } return p, nil diff --git a/auth/auth_test.go b/auth/auth_test.go index 417a3212..c1cc5208 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -192,11 +192,11 @@ func TestCustomTokenInvalidCredential(t *testing.T) { } } -func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { +func TestVerifyIDTokenAndCheckRevokedValid(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() - ft, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, testIDToken) + ft, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, testIDToken) if err != nil { t.Error(err) } @@ -208,7 +208,7 @@ func TestVerifyIDTokenWithCheckRevokedValid(t *testing.T) { } } -func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { +func TestVerifyIDTokenAndCheckRevokedDoNotCheck(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token @@ -225,15 +225,15 @@ func TestVerifyIDTokenWithCheckRevokedDoNotCheck(t *testing.T) { } } -func TestVerifyIDTokenWithCheckRevokedInvalidated(t *testing.T) { +func TestVerifyIDTokenAndCheckRevokedInvalidated(t *testing.T) { s := echoServer(testGetUserResponse, t) defer s.Close() tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token - p, err := s.Client.VerifyIDTokenWithCheckRevoked(ctx, tok) + p, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, tok) we := "id token has been revoked" if p != nil || err == nil || err.Error() != we { - t.Errorf("VerifyIDTokenWithCheckRevoked(ctx, token) =(%v, %v); want = (%v, %v)", + t.Errorf("VerifyIDTokenAndCheckRevoked(ctx, token) =(%v, %v); want = (%v, %v)", p, err, nil, we) } } diff --git a/auth/user_mgt.go b/auth/user_mgt.go index bcc1701d..216e90a7 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -76,12 +76,12 @@ type UserMetadata struct { // UserRecord contains metadata associated with a Firebase user account. type UserRecord struct { *UserInfo - CustomClaims map[string]interface{} - Disabled bool - EmailVerified bool - ProviderUserInfo []*UserInfo - TokensValidAfterTime int64 // milliseconds since epoch. - UserMetadata *UserMetadata + CustomClaims map[string]interface{} + Disabled bool + EmailVerified bool + ProviderUserInfo []*UserInfo + TokensValidAfterMillis int64 // milliseconds since epoch. + UserMetadata *UserMetadata } // ExportedUserRecord is the returned user value used when listing all the users. @@ -619,11 +619,11 @@ func makeExportedUser(r *identitytoolkit.UserInfo) (*ExportedUserRecord, error) ProviderID: defaultProviderID, UID: r.LocalId, }, - CustomClaims: cc, - Disabled: r.Disabled, - EmailVerified: r.EmailVerified, - ProviderUserInfo: providerUserInfo, - TokensValidAfterTime: r.ValidSince * 1000, + CustomClaims: cc, + Disabled: r.Disabled, + EmailVerified: r.EmailVerified, + ProviderUserInfo: providerUserInfo, + TokensValidAfterMillis: r.ValidSince * 1000, UserMetadata: &UserMetadata{ LastLogInTimestamp: r.LastLoginAt, CreationTimestamp: r.CreatedAt, diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 95f94345..f1789e84 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -60,7 +60,7 @@ var testUser = &UserRecord{ UID: "testuid", }, }, - TokensValidAfterTime: 1494364393000, + TokensValidAfterMillis: 1494364393000, UserMetadata: &UserMetadata{ CreationTimestamp: 1234567890, LastLogInTimestamp: 1233211232, diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 980999ef..86fed53f 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -80,7 +80,7 @@ func TestCustomToken(t *testing.T) { } } -func TestVerifyIDTokenWithCheckRevoked(t *testing.T) { +func TestVerifyIDTokenAndCheckRevoked(t *testing.T) { uid := "user_revoked" ct, err := client.CustomToken(uid) @@ -92,7 +92,7 @@ func TestVerifyIDTokenWithCheckRevoked(t *testing.T) { t.Fatal(err) } ctx := context.Background() - vt, err := client.VerifyIDTokenWithCheckRevoked(ctx, idt) + vt, err := client.VerifyIDTokenAndCheckRevoked(ctx, idt) if err != nil { t.Fatal(err) } @@ -102,17 +102,17 @@ func TestVerifyIDTokenWithCheckRevoked(t *testing.T) { // The backend stores the validSince property in seconds since the epoch. // The issuedAt property of the token is also in seconds, if a token was // issued, and then in the same second tokens were revoked, the token will - // have the same timestamp as the tokensValidAfterTime, and will therefore + // have the same timestamp as the tokensValidAfterMillis, and will therefore // not be considered revoked. Hence we wait one second before revoking. time.Sleep(time.Second) if err = client.RevokeRefreshTokens(ctx, uid); err != nil { t.Fatal(err) } - vt, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt) + vt, err = client.VerifyIDTokenAndCheckRevoked(ctx, idt) we := "id token has been revoked" if vt != nil || err == nil || err.Error() != we { - t.Errorf("tok, err := VerifyIDTokenWithCheckRevoked(); got (%v, %s) ; want (%v, %v)", + t.Errorf("tok, err := VerifyIDTokenAndCheckRevoked(); got (%v, %s) ; want (%v, %v)", vt, err, nil, we) } @@ -126,8 +126,8 @@ func TestVerifyIDTokenWithCheckRevoked(t *testing.T) { t.Fatal(err) } - if _, err = client.VerifyIDTokenWithCheckRevoked(ctx, idt); err != nil { - t.Errorf("VerifyIDTokenWithCheckRevoked(); err = %s; want err = ", err) + if _, err = client.VerifyIDTokenAndCheckRevoked(ctx, idt); err != nil { + t.Errorf("VerifyIDTokenAndCheckRevoked(); err = %s; want err = ", err) } err = client.DeleteUser(ctx, uid) diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index f6d81375..b0ce94a3 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -89,11 +89,11 @@ func testCreateUsers(t *testing.T) { t.Fatal(err) } testFixtures.uidList = append(testFixtures.uidList, u.UID) - // make sure that the user.TokensValidAfterTime is not in the future or stale. - if u.TokensValidAfterTime > time.Now().Unix()*1000 { + // make sure that the user.TokensValidAfterMillis is not in the future or stale. + if u.TokensValidAfterMillis > time.Now().Unix()*1000 { t.Errorf("timestamp cannot be in the future") } - if time.Now().Sub(time.Unix(u.TokensValidAfterTime, 0)) > time.Hour { + if time.Now().Sub(time.Unix(u.TokensValidAfterMillis, 0)) > time.Hour { t.Errorf("timestamp should be recent") } @@ -254,7 +254,7 @@ func testUpdateUser(t *testing.T) { UID: testFixtures.sampleUserBlank.UID, ProviderID: "firebase", }, - TokensValidAfterTime: u.TokensValidAfterTime, + TokensValidAfterMillis: u.TokensValidAfterMillis, UserMetadata: &auth.UserMetadata{ CreationTimestamp: testFixtures.sampleUserBlank.UserMetadata.CreationTimestamp, }, @@ -286,7 +286,7 @@ func testUpdateUser(t *testing.T) { ProviderID: "firebase", Email: "abc@ab.ab", }, - TokensValidAfterTime: u.TokensValidAfterTime, + TokensValidAfterMillis: u.TokensValidAfterMillis, UserMetadata: &auth.UserMetadata{ CreationTimestamp: testFixtures.sampleUserBlank.UserMetadata.CreationTimestamp, }, From a7ebce742fac5600ddfa25842fa10d9d70f7b35a Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 2 Feb 2018 16:24:46 -0500 Subject: [PATCH 25/31] Change timestamp tests to millis --- auth/user_mgt_test.go | 8 ++++---- testdata/get_user.json | 4 ++-- testdata/list_users.json | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index f1789e84..09e601a2 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -62,8 +62,8 @@ var testUser = &UserRecord{ }, TokensValidAfterMillis: 1494364393000, UserMetadata: &UserMetadata{ - CreationTimestamp: 1234567890, - LastLogInTimestamp: 1233211232, + CreationTimestamp: 1234567890000, + LastLogInTimestamp: 1233211230000, }, CustomClaims: map[string]interface{}{"admin": true, "package": "gold"}, } @@ -646,8 +646,8 @@ func TestMakeExportedUser(t *testing.T) { PasswordHash: "passwordhash", ValidSince: 1494364393, Disabled: false, - CreatedAt: 1234567890, - LastLoginAt: 1233211232, + CreatedAt: 1234567890000, + LastLoginAt: 1233211232000, CustomAttributes: `{"admin": true, "package": "gold"}`, ProviderUserInfo: []*identitytoolkit.UserInfoProviderUserInfo{ { diff --git a/testdata/get_user.json b/testdata/get_user.json index a56ef9f3..a62102e0 100644 --- a/testdata/get_user.json +++ b/testdata/get_user.json @@ -28,8 +28,8 @@ "passwordUpdatedAt": 1.494364393E+12, "validSince": "1494364393", "disabled": false, - "createdAt": "1234567890", - "lastLoginAt": "1233211232", + "createdAt": "1234567890000", + "lastLoginAt": "1233211232000", "customAttributes": "{\"admin\": true, \"package\": \"gold\"}" } ] diff --git a/testdata/list_users.json b/testdata/list_users.json index 21d152fc..a0c625ef 100644 --- a/testdata/list_users.json +++ b/testdata/list_users.json @@ -28,8 +28,8 @@ "passwordUpdatedAt": 1.494364393E+12, "validSince": "1494364393", "disabled": false, - "createdAt": "1234567890", - "lastLoginAt": "1233211232", + "createdAt": "1234567890000", + "lastLoginAt": "1233211232000", "customAttributes": "{\"admin\": true, \"package\": \"gold\"}" }, { @@ -59,8 +59,8 @@ "passwordUpdatedAt": 1.494364393E+12, "validSince": "1494364393", "disabled": false, - "createdAt": "1234567890", - "lastLoginAt": "1233211232", + "createdAt": "1234567890000", + "lastLoginAt": "1233211232000", "customAttributes": "{\"admin\": true, \"package\": \"gold\"}" }, { @@ -90,8 +90,8 @@ "passwordUpdatedAt": 1.494364393E+12, "validSince": "1494364393", "disabled": false, - "createdAt": "1234567890", - "lastLoginAt": "1233211232", + "createdAt": "1234567890000", + "lastLoginAt": "1233211232000", "customAttributes": "{\"admin\": true, \"package\": \"gold\"}" } ], From 23a5c7dda1667fd03c673235343d1343deb376fc Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Fri, 2 Feb 2018 17:21:05 -0500 Subject: [PATCH 26/31] millis fix --- auth/user_mgt_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/auth/user_mgt_test.go b/auth/user_mgt_test.go index 09e601a2..db7b7300 100644 --- a/auth/user_mgt_test.go +++ b/auth/user_mgt_test.go @@ -63,7 +63,7 @@ var testUser = &UserRecord{ TokensValidAfterMillis: 1494364393000, UserMetadata: &UserMetadata{ CreationTimestamp: 1234567890000, - LastLogInTimestamp: 1233211230000, + LastLogInTimestamp: 1233211232000, }, CustomClaims: map[string]interface{}{"admin": true, "package": "gold"}, } @@ -674,7 +674,8 @@ func TestMakeExportedUser(t *testing.T) { } if !reflect.DeepEqual(exported.UserRecord, want.UserRecord) { // zero in - t.Errorf("makeExportedUser() = %#v; want: %#v", exported.UserRecord, want.UserRecord) + t.Errorf("makeExportedUser() = %#v; want: %#v \n(%#v)\n(%#v)", exported.UserRecord, want.UserRecord, + exported.UserMetadata, want.UserMetadata) } if exported.PasswordHash != want.PasswordHash { t.Errorf("PasswordHash = %q; want = %q", exported.PasswordHash, want.PasswordHash) From b1c6caad588f35ea6fc6f935494a0bc8f13322be Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 5 Feb 2018 09:56:38 -0500 Subject: [PATCH 27/31] fixes --- CHANGELOG.md | 7 ++++--- auth/auth.go | 12 +++++++----- auth/user_mgt.go | 4 ++-- integration/auth/auth_test.go | 2 +- integration/auth/user_mgt_test.go | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bcce323..556cef25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,12 @@ ### Token revokaction - [added] A New ['VerifyIDTokenAndCheckRevoked(ctx, token)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) - method checks to see if the token has been revoked. + method has been added to check for revoked ID tokens. - [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens) - has been added to invalidate all tokens issued before the current second. + has been added to invalidate all refresh tokens issued to a user. - [added] A new property - `TokensValidAfterMillis` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord) + `TokensValidAfterMillis` has been added to the ['UserRecord'](https://godoc.org/firebase.google.com/go/auth#UserRecord). + This property stores the time of the revocation truncated to 1 second accuracy. # v2.4.0 diff --git a/auth/auth.go b/auth/auth.go index 71eee91d..5c77bc1b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -179,10 +179,12 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac // RevokeRefreshTokens revokes all refresh tokens for the specified user. // -// RevokeRefreshTokens revokes all refresh tokens for the specified user. -// In addition to revoking all refresh tokens for a user, all ID tokens issued -// before revocation will also be revoked at the Auth backend. Any request with an -// ID token generated before revocation will be rejected with a token expired error. +// RevokeRefreshTokens updates the user's TokensValidAfterMillis to the current UTC second. +// It is important that the server on which this is called has its clock set correctly and synchronized. +// +// While this revokes all sessions for a specified user and disables any new ID tokens for existing sessions +// from getting minted, existing ID tokens may remain active until their natural expiration (one hour). +// To verify that ID tokens are revoked, use `verifyIdTokenAndCheckRevoked(ctx, idToken)`. func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { return c.updateUser(ctx, uid, (&UserToUpdate{}).revokeRefreshTokens()) } @@ -194,7 +196,7 @@ func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { // a Token containing the decoded claims in the input JWT. See // https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for // more details on how to obtain an ID token in a client app. -// This does not check whether or not the token has been revoked, see VerifyIDTokenAndCheckRevoked below. +// This does not check whether or not the token has been revoked. see `VerifyIDTokenAndCheckRevoked` below. func (c *Client) VerifyIDToken(idToken string) (*Token, error) { if c.projectID == "" { return nil, errors.New("project id not available") diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 216e90a7..37a3f428 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -428,8 +428,8 @@ func validatePhone(val interface{}) error { } func validateValidSince(val interface{}) error { - if _, ok := val.(int64); !ok { - return fmt.Errorf("validSince must be an integer signifying seconds since the epoch") + if v, ok := val.(int64); !ok || v <= 0 { + return fmt.Errorf("validSince must be an positive integer signifying seconds since the epoch") } return nil } diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index 86fed53f..ccbd8fae 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -100,7 +100,7 @@ func TestVerifyIDTokenAndCheckRevoked(t *testing.T) { t.Errorf("UID = %q; want UID = %q", vt.UID, uid) } // The backend stores the validSince property in seconds since the epoch. - // The issuedAt property of the token is also in seconds, if a token was + // The issuedAt property of the token is also in seconds. If a token was // issued, and then in the same second tokens were revoked, the token will // have the same timestamp as the tokensValidAfterMillis, and will therefore // not be considered revoked. Hence we wait one second before revoking. diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index b0ce94a3..1f099a34 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -51,7 +51,7 @@ func TestUserManagement(t *testing.T) { {"Add custom claims", testAddCustomClaims}, {"Delete test users", testDeleteUsers}, } - // The tests are meant to be run in sequence, a failure in creating the users + // The tests are meant to be run in sequence. A failure in creating the users // should be fatal so non of the other tests run. However calling Fatal from a // subtest does not prevent the other subtests from running, hence we check the // success of each subtest before proceeding. From bf6b1ab9875fb6cb523a6c544cea5d33834ca286 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Mon, 5 Feb 2018 10:00:45 -0500 Subject: [PATCH 28/31] remove existing user check --- integration/auth/user_mgt_test.go | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/integration/auth/user_mgt_test.go b/integration/auth/user_mgt_test.go index 1f099a34..3013fbe0 100644 --- a/integration/auth/user_mgt_test.go +++ b/integration/auth/user_mgt_test.go @@ -18,7 +18,6 @@ package auth import ( "fmt" "reflect" - "strings" "testing" "time" @@ -62,20 +61,6 @@ func TestUserManagement(t *testing.T) { } } -func forceCreateUser(uid string, params *auth.UserToCreate) (*auth.UserRecord, error) { - u, err := client.CreateUser(context.Background(), params) - if err != nil { - if strings.Contains(err.Error(), "DUPLICATE_LOCAL_ID") { - if err = client.DeleteUser(context.Background(), uid); err != nil { - return nil, err - } - return client.CreateUser(context.Background(), params) - } - return nil, err - } - return u, nil -} - // N.B if the tests are failing due to inability to create existing users, manual // cleanup of the previus test run might be required, delete the unwanted users via: // https://console.firebase.google.com/u/0/project//authentication/users @@ -84,7 +69,7 @@ func testCreateUsers(t *testing.T) { for i := 0; i < 3; i++ { uid := fmt.Sprintf("tempTestUserID-%d", i) params := (&auth.UserToCreate{}).UID(uid) - u, err := forceCreateUser(uid, params) + u, err := client.CreateUser(context.Background(), params) if err != nil { t.Fatal(err) } @@ -113,7 +98,8 @@ func testCreateUsers(t *testing.T) { Email(uid + "email@test.com"). DisplayName("display_name"). Password("password") - if u, err = forceCreateUser(uid, params); err != nil { + + if u, err = client.CreateUser(context.Background(), params); err != nil { t.Fatal(err) } testFixtures.sampleUserWithData = u From 0bf8041ca17ff2ba6a893daf4b53d9cb96780af0 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 6 Feb 2018 13:42:22 -0500 Subject: [PATCH 29/31] PR comments --- auth/auth.go | 4 ++-- auth/user_mgt.go | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 5c77bc1b..92f3880c 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -177,7 +177,7 @@ func (c *Client) CustomTokenWithClaims(uid string, devClaims map[string]interfac return encodeToken(c.snr, defaultHeader(), payload) } -// RevokeRefreshTokens revokes all refresh tokens for the specified user. +// RevokeRefreshTokens revokes all refresh tokens issued to a user. // // RevokeRefreshTokens updates the user's TokensValidAfterMillis to the current UTC second. // It is important that the server on which this is called has its clock set correctly and synchronized. @@ -196,7 +196,7 @@ func (c *Client) RevokeRefreshTokens(ctx context.Context, uid string) error { // a Token containing the decoded claims in the input JWT. See // https://firebase.google.com/docs/auth/admin/verify-id-tokens#retrieve_id_tokens_on_clients for // more details on how to obtain an ID token in a client app. -// This does not check whether or not the token has been revoked. see `VerifyIDTokenAndCheckRevoked` below. +// This does not check whether or not the token has been revoked. See `VerifyIDTokenAndCheckRevoked` below. func (c *Client) VerifyIDToken(idToken string) (*Token, error) { if c.projectID == "" { return nil, errors.New("project id not available") diff --git a/auth/user_mgt.go b/auth/user_mgt.go index 37a3f428..423ceece 100644 --- a/auth/user_mgt.go +++ b/auth/user_mgt.go @@ -40,7 +40,7 @@ var commonValidators = map[string]func(interface{}) error{ "password": validatePassword, "photoUrl": validatePhotoURL, "localId": validateUID, - "validSince": validateValidSince, + "validSince": func(interface{}) error { return nil }, // Needed for preparePayload. } // Create a new interface @@ -427,13 +427,6 @@ func validatePhone(val interface{}) error { return nil } -func validateValidSince(val interface{}) error { - if v, ok := val.(int64); !ok || v <= 0 { - return fmt.Errorf("validSince must be an positive integer signifying seconds since the epoch") - } - return nil -} - func (u *UserToCreate) preparePayload(user *identitytoolkit.IdentitytoolkitRelyingpartySignupNewUserRequest) error { params := map[string]interface{}{} if u.params == nil { From 0b91a4aab31f596efdeae3dbde7c4c0afc323c68 Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 6 Feb 2018 15:51:38 -0500 Subject: [PATCH 30/31] capitalization --- auth/auth.go | 2 +- auth/auth_test.go | 2 +- integration/auth/auth_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 92f3880c..f6605c7b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -266,7 +266,7 @@ func (c *Client) VerifyIDTokenAndCheckRevoked(ctx context.Context, idToken strin } if p.IssuedAt*1000 < user.TokensValidAfterMillis { - return nil, fmt.Errorf("id token has been revoked") + return nil, fmt.Errorf("ID token has been revoked") } return p, nil } diff --git a/auth/auth_test.go b/auth/auth_test.go index c1cc5208..690b5d6f 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -231,7 +231,7 @@ func TestVerifyIDTokenAndCheckRevokedInvalidated(t *testing.T) { tok := getIDToken(mockIDTokenPayload{"uid": "uid", "iat": 1970}) // old token p, err := s.Client.VerifyIDTokenAndCheckRevoked(ctx, tok) - we := "id token has been revoked" + we := "ID token has been revoked" if p != nil || err == nil || err.Error() != we { t.Errorf("VerifyIDTokenAndCheckRevoked(ctx, token) =(%v, %v); want = (%v, %v)", p, err, nil, we) diff --git a/integration/auth/auth_test.go b/integration/auth/auth_test.go index ccbd8fae..5e027d44 100644 --- a/integration/auth/auth_test.go +++ b/integration/auth/auth_test.go @@ -110,7 +110,7 @@ func TestVerifyIDTokenAndCheckRevoked(t *testing.T) { } vt, err = client.VerifyIDTokenAndCheckRevoked(ctx, idt) - we := "id token has been revoked" + we := "ID token has been revoked" if vt != nil || err == nil || err.Error() != we { t.Errorf("tok, err := VerifyIDTokenAndCheckRevoked(); got (%v, %s) ; want (%v, %v)", vt, err, nil, we) From f20054e2e48284339e2ce27f989b80ae4c39514a Mon Sep 17 00:00:00 2001 From: Avishalom Shalit Date: Tue, 6 Feb 2018 17:27:00 -0500 Subject: [PATCH 31/31] typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 556cef25..06b8da26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -### Token revokaction +### Token revocation - [added] A New ['VerifyIDTokenAndCheckRevoked(ctx, token)'](https://godoc.org/firebase.google.com/go/auth#Client.VerifyIDToken) method has been added to check for revoked ID tokens. - [added] A new method ['RevokeRefreshTokens(uid)'](https://godoc.org/firebase.google.com/go/auth#Client.RevokeRefreshTokens)