Skip to content

Commit

Permalink
refresh token expiration: bugfix and migration
Browse files Browse the repository at this point in the history
  • Loading branch information
hasan7n committed Aug 9, 2024
1 parent c847aac commit 6929f46
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 9 deletions.
15 changes: 14 additions & 1 deletion cli/medperf/account_management/account_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,29 @@ def set_credentials(
id_token_payload,
token_issued_at,
token_expires_in,
login_event=False,
):
email = id_token_payload["email"]
TokenStore().set_tokens(email, access_token, refresh_token)
config_p = read_config()

if login_event:
# Set the time the user logged in, so that we can track the lifetime of
# the refresh token
logged_in_at = token_issued_at
else:
# This means this is a refresh event. Preserve the logged_in_at timestamp.
logged_in_at = config_p.active_profile[config.credentials_keyword][
"logged_in_at"
]

account_info = {
"email": email,
"token_issued_at": token_issued_at,
"token_expires_in": token_expires_in,
"logged_in_at": logged_in_at,
}
config_p = read_config()

config_p.active_profile[config.credentials_keyword] = account_info
write_config(config_p)

Expand Down
29 changes: 22 additions & 7 deletions cli/medperf/comms/auth/auth0.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def login(self, email):
id_token_payload,
token_issued_at,
token_expires_in,
login_event=True,
)

def __request_device_code(self):
Expand Down Expand Up @@ -191,16 +192,30 @@ def _access_token(self):
refresh_token = creds["refresh_token"]
token_expires_in = creds["token_expires_in"]
token_issued_at = creds["token_issued_at"]
leeway_time = token_issued_at + token_expires_in - config.token_expiration_leeway
absolute_expiration_time = token_issued_at + config.token_absolute_expiry
logged_in_at = creds["logged_in_at"]

# token_issued_at and expires_in are for the access token
sliding_expiration_time = (
token_issued_at + token_expires_in - config.token_expiration_leeway
)
absolute_expiration_time = (
logged_in_at
+ config.token_absolute_expiry
- config.refresh_token_expiration_leeway
)
current_time = time.time()
if current_time > leeway_time and current_time <= absolute_expiration_time:
access_token = self.__refresh_access_token(refresh_token)
elif current_time > absolute_expiration_time:
# Expired token. Force logout and ask the user to re-authenticate
logging.debug(f"Token expired: {absolute_expiration_time=} <> {current_time=}")
if current_time > absolute_expiration_time:
# Expired refresh token. Force logout and ask the user to re-authenticate
logging.debug(
f"Refresh token expired: {absolute_expiration_time=} <> {current_time=}"
)
self.logout()
raise AuthenticationError("Token expired. Please re-authenticate")

if current_time > sliding_expiration_time:
# Expired access token. Refresh it.
access_token = self.__refresh_access_token(refresh_token)

return access_token

def __refresh_access_token(self, refresh_token):
Expand Down
3 changes: 2 additions & 1 deletion cli/medperf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
auth_jwks_cache_ttl = 600 # fetch jwks every 10 mins. Default value in auth0 python SDK

token_expiration_leeway = 10 # Refresh tokens 10 seconds before expiration
token_absolute_expiry = 2592000 # Maximum lifetime of a given token. This value is set on auth0's configuration
refresh_token_expiration_leeway = 10 # Logout users 10 seconds before absolute token expiration.
token_absolute_expiry = 2592000 # Refresh token absolute expiration time (seconds). This value is set on auth0's configuration
access_token_storage_id = "medperf_access_token"
refresh_token_storage_id = "medperf_refresh_token"

Expand Down
14 changes: 14 additions & 0 deletions cli/medperf/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import shutil
import time

from medperf import config
from medperf.config_management import read_config, write_config
Expand All @@ -25,6 +26,7 @@ def apply_configuration_migrations():

config_p = read_config()

# Migration for moving the logs folder to a new location
if "logs_folder" not in config_p.storage:
return

Expand All @@ -35,4 +37,16 @@ def apply_configuration_migrations():

del config_p.storage["logs_folder"]

# Migration for tracking the login timestamp (i.e., refresh token issuance timestamp)
if config.credentials_keyword in config_p.active_profile:
# So the user is logged in
if "logged_in_at" not in config_p.active_profile[config.credentials_keyword]:
# Apply migration. We will set it to the current time, since this
# will make sure they will not be logged out before the actual refresh
# token expiration (for a better user experience). However, currently logged
# in users will still face a confusing error when the refresh token expires.
config_p.active_profile[config.credentials_keyword][
"logged_in_at"
] = time.time()

write_config(config_p)

0 comments on commit 6929f46

Please sign in to comment.