Skip to content

Commit c5f57d0

Browse files
committed
add last_refresh_timestamp
1 parent a83f66e commit c5f57d0

File tree

3 files changed

+43
-4
lines changed

3 files changed

+43
-4
lines changed

firebase_admin/_user_mgt.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from urllib import parse
2020

2121
import requests
22+
import iso8601
2223

2324
from firebase_admin import _auth_utils
2425
from firebase_admin import _identifier
@@ -42,11 +43,14 @@ def __init__(self, description):
4243
class UserMetadata:
4344
"""Contains additional metadata associated with a user account."""
4445

45-
def __init__(self, creation_timestamp=None, last_sign_in_timestamp=None):
46+
def __init__(self, creation_timestamp=None, last_sign_in_timestamp=None,
47+
last_refresh_timestamp=None):
4648
self._creation_timestamp = _auth_utils.validate_timestamp(
4749
creation_timestamp, 'creation_timestamp')
4850
self._last_sign_in_timestamp = _auth_utils.validate_timestamp(
4951
last_sign_in_timestamp, 'last_sign_in_timestamp')
52+
self._last_refresh_timestamp = _auth_utils.validate_timestamp(
53+
last_refresh_timestamp, 'last_refresh_timestamp')
5054

5155
@property
5256
def creation_timestamp(self):
@@ -66,6 +70,16 @@ def last_sign_in_timestamp(self):
6670
"""
6771
return self._last_sign_in_timestamp
6872

73+
@property
74+
def last_refresh_timestamp(self):
75+
"""The time at which the user was last active (ID token refreshed).
76+
77+
Returns:
78+
integer: Milliseconds since epoch timestamp, or None if the user was
79+
never active.
80+
"""
81+
return self._last_refresh_timestamp
82+
6983

7084
class UserInfo:
7185
"""A collection of standard profile information for a user.
@@ -217,7 +231,12 @@ def _int_or_none(key):
217231
if key in self._data:
218232
return int(self._data[key])
219233
return None
220-
return UserMetadata(_int_or_none('createdAt'), _int_or_none('lastLoginAt'))
234+
last_refresh_at_millis = None
235+
last_refresh_at_iso8601 = self._data.get('lastRefreshAt', None)
236+
if last_refresh_at_iso8601 is not None:
237+
last_refresh_at_millis = iso8601.parse_date(last_refresh_at_iso8601).timestamp() * 1000
238+
return UserMetadata(
239+
_int_or_none('createdAt'), _int_or_none('lastLoginAt'), last_refresh_at_millis)
221240

222241
@property
223242
def provider_data(self):

integration/test_auth.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def _sign_in(custom_token, api_key):
4747
return resp.json().get('idToken')
4848

4949
def _sign_in_with_password(email, password, api_key):
50-
body = {'email': email, 'password': password}
50+
body = {'email': email, 'password': password, 'returnSecureToken': True}
5151
params = {'key' : api_key}
5252
resp = requests.request('post', _verify_password_url, params=params, json=body)
5353
resp.raise_for_status()
@@ -163,7 +163,7 @@ def new_user():
163163
auth.delete_user(user.uid)
164164

165165
@pytest.fixture
166-
def new_user_with_params():
166+
def new_user_with_params() -> auth.UserRecord:
167167
random_id, email = _random_id()
168168
phone = _random_phone()
169169
user = auth.create_user(
@@ -289,6 +289,25 @@ def test_de_dups_duplicate_users(self, new_user):
289289
expected = list(map(self._map_user_record_to_uid_email_phones, [new_user]))
290290
assert actual == expected
291291

292+
def test_last_refresh_timestamp(new_user_with_params: auth.UserRecord, api_key):
293+
# new users should not have a last_refresh_timestamp set
294+
assert new_user_with_params.user_metadata.last_refresh_timestamp is None
295+
296+
# login to cause the last_refresh_timestamp to be set
297+
_sign_in_with_password(new_user_with_params.email, 'secret', api_key)
298+
new_user_with_params = auth.get_user(new_user_with_params.uid)
299+
300+
# Ensure the last refresh time occurred at approximately 'now'. (With a
301+
# tolerance of up to 1 minute; we ideally want to ensure that any timezone
302+
# considerations are handled properly, so as long as we're within an hour,
303+
# we're in good shape.)
304+
millis_per_second = 1000
305+
seconds_per_minute = 60
306+
millis_per_minute = millis_per_second * seconds_per_minute
307+
308+
last_refresh_timestamp = new_user_with_params.user_metadata.last_refresh_timestamp
309+
assert last_refresh_timestamp == pytest.approx(
310+
time.time()*millis_per_second, 1*millis_per_minute)
292311

293312
def test_list_users(new_user_list):
294313
err_msg_template = (

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ google-api-core[grpc] >= 1.14.0, < 2.0.0dev; platform.python_implementation != '
88
google-api-python-client >= 1.7.8
99
google-cloud-firestore >= 1.4.0; platform.python_implementation != 'PyPy'
1010
google-cloud-storage >= 1.18.0
11+
iso8601 >= 0.1.12

0 commit comments

Comments
 (0)