Skip to content

Commit

Permalink
fix(auth): add support for setting auth header and cookie manually
Browse files Browse the repository at this point in the history
  • Loading branch information
ryeguard committed Mar 25, 2024
1 parent b298da8 commit 1169037
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 5 deletions.
14 changes: 14 additions & 0 deletions garminexport/cli/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ def parse_args() -> argparse.Namespace:
help=("The maximum number of retries to make on failed attempts to fetch an activity. "
"Exponential backoff will be used, meaning that the delay between successive attempts "
"will double with every retry, starting at one second. DEFAULT: {}").format(DEFAULT_MAX_RETRIES))
parser.add_argument(
"--token",
default=None,
type=str,
help=("Authentication header token. Use with 'jwt_fgp' instead of username and password, for example "
"if login fails due to ReCaptcha."))
parser.add_argument(
"--jwt_fgp",
default=None,
type=str,
help=("Authentication JWT_FGP Cookie. Use with 'token' instead of username and password, for example "
"if login fails due to ReCaptcha."))

return parser.parse_args()

Expand All @@ -69,6 +81,8 @@ def main():
try:
incremental_backup(username=args.username,
password=args.password,
token=args.token,
jwt_fgp=args.jwt_fgp,
backup_dir=args.backup_dir,
export_formats=args.format,
ignore_errors=args.ignore_errors,
Expand Down
17 changes: 15 additions & 2 deletions garminexport/cli/get_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ def main():
"--log-level", metavar="LEVEL", type=str,
help="Desired log output level (DEBUG, INFO, WARNING, ERROR). Default: INFO.",
default="INFO")
parser.add_argument(
"--token",
default=None,
type=str,
help=("Authentication header token. Use with 'jwt_fgp' instead of username and password, for example "
"if login fails due to ReCaptcha."))
parser.add_argument(
"--jwt_fgp",
default=None,
type=str,
help=("Authentication JWT_FGP Cookie. Use with 'token' instead of username and password, for example "
"if login fails due to ReCaptcha."))

args = parser.parse_args()

Expand All @@ -60,10 +72,11 @@ def main():
if not os.path.isdir(args.destination):
os.makedirs(args.destination)

if not args.password:
prompt_password = not args.password and (not args.token or not args.jwt_fgp)
if prompt_password:
args.password = getpass.getpass("Enter password: ")

with GarminClient(args.username, args.password) as client:
with GarminClient(args.username, args.password, args.token, args.jwt_fgp) as client:
log.info("fetching activity %s ...", args.activity)
summary = client.get_activity_summary(args.activity)
# set up a retryer that will handle retries of failed activity downloads
Expand Down
20 changes: 19 additions & 1 deletion garminexport/garminclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class GarminClient(object):
"""

def __init__(self, username, password):
def __init__(self, username, password, token=None, jwt_fgp=None):
"""Initialize a :class:`GarminClient` instance.
:param username: Garmin Connect user name or email address.
Expand All @@ -76,6 +76,8 @@ def __init__(self, username, password):
"""
self.username = username
self.password = password
self.token = token
self.jwt_fgp = jwt_fgp

self.session = None

Expand All @@ -89,6 +91,11 @@ def __exit__(self, exc_type, exc_value, traceback):

def connect(self):
self.session = new_http_session()

if self.token is not None and self.jwt_fgp is not None:
self._authenticate_with_token()
return

self._authenticate()

def disconnect(self):
Expand Down Expand Up @@ -127,6 +134,17 @@ def _authenticate(self):
'Di-Backend': 'connectapi.garmin.com',
})

def _authenticate_with_token(self):
log.info("authenticating user with provided token ...")
token_type = "Bearer"
self.session.headers.update(
{
"Authorization": f"{token_type} {self.token}",
"Di-Backend": "connectapi.garmin.com",
"NK": "NT",
}
)
self.session.cookies.update({"JWT_FGP": self.jwt_fgp})

def _get_oauth_token(self):
"""Retrieve an OAuth token to use for the session.
Expand Down
7 changes: 5 additions & 2 deletions garminexport/incremental_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

def incremental_backup(username: str,
password: str = None,
token: str = None,
jwt_fgp: str = None,
backup_dir: str = os.path.join(".", "activities"),
export_formats: List[str] = None,
ignore_errors: bool = False,
Expand Down Expand Up @@ -43,15 +45,16 @@ def incremental_backup(username: str,
if not os.path.isdir(backup_dir):
os.makedirs(backup_dir)

if not password:
prompt_password = not password and (not token or not jwt_fgp)
if prompt_password:
password = getpass.getpass("Enter password: ")

# set up a retryer that will handle retries of failed activity downloads
retryer = Retryer(
delay_strategy=ExponentialBackoffDelayStrategy(initial_delay=timedelta(seconds=1)),
stop_strategy=MaxRetriesStopStrategy(max_retries))

with GarminClient(username, password) as client:
with GarminClient(username, password, token, jwt_fgp) as client:
# get all activity ids and timestamps from Garmin account
log.info("scanning activities for %s ...", username)
activities = set(retryer.call(client.list_activities))
Expand Down

0 comments on commit 1169037

Please sign in to comment.