Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(auth): add support for setting auth header and cookie manually #115

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading