Skip to content

Commit

Permalink
Update Strava authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
oldnapalm committed Mar 27, 2024
1 parent 3497a10 commit 1152daa
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 46 deletions.
19 changes: 1 addition & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,28 +235,11 @@ To obtain your current profile:
<details><summary>Expand</summary>

* Get CLIENT_ID and CLIENT_SECRET from https://www.strava.com/settings/api

<details><summary>Using launcher (Windows and macOS only)</summary>

* Set the authorization callback domain of your API application to ``launcher.zwift.com``
* Create a ``strava-api.txt`` file in the ``storage`` directory containing your client ID and secret
```
CLIENT_ID
CLIENT_SECRET
```
* Use the "Settings - Strava" button in the launcher window to authorize.

</details>

<details><summary>Using strava_auth script</summary>

* __NOTE:__ instead of performing the steps below you can instead set the authorization callback domain of your API application to ``launcher.zwift.com`` and use the "Settings - Strava" button in the launcher window (Windows and macOS only).
* Run ``scripts/strava_auth.py --client-id CLIENT_ID --client-secret CLIENT_SECRET``
* Or, if using the Windows zoffline.exe version without Python installed you can run ``strava_auth.exe`` obtained from https://github.com/zoffline/zwift-offline/releases/tag/zoffline_helper in place of ``scripts/strava_auth.py``
* Open http://localhost:8000/ and authorize.
* Move the resulting ``strava_token.txt`` (saved in whatever directory you ran ``strava_auth.py`` in) into the ``storage/1`` directory.

</details>

* If testing, ride at least 300 meters, shorter activities won't be uploaded.

</details>
Expand Down
8 changes: 1 addition & 7 deletions cdn/static/web/launcher/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ <h4 class="text-shadow">Logged in as {{ username }}</h4>
{% endif %}
<a href="{{ url_for('power_curves', username=username) }}" class="btn btn-sm btn-secondary">Power curves</a>
<a href="{{ url_for('profile', username=username) }}" class="btn btn-sm btn-secondary">Zwift</a>
{% if api %}
{% if not token %}
<a href="{{ url_for('strava') }}" class="btn btn-sm btn-secondary">Strava</a>
{% else %}
<a href="/delete/strava_token.txt" class="btn btn-sm btn-danger">Remove Strava token</a>
{% endif %}
{% endif %}
<a href="{{ url_for('strava', username=username) }}" class="btn btn-sm btn-secondary">Strava</a>
<a href="{{ url_for('garmin', username=username) }}" class="btn btn-sm btn-secondary">Garmin</a>
<a href="{{ url_for('intervals', username=username) }}" class="btn btn-sm btn-secondary">Intervals</a>
</div>
Expand Down
52 changes: 52 additions & 0 deletions cdn/static/web/launcher/strava.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{% extends "./layout.html" %}
{% block content %}
<h1><div class="text-shadow">Strava</div></h1>
{% if username != "zoffline" %}
<h4 class="text-shadow">Logged in as {{ username }}</h4>
{% endif %}
<div class="row">
<div class="col-md-12">
<a href="{{ url_for('settings', username=username) }}" class="btn btn-sm btn-secondary">Back</a>
{% if cid or cs %}
<a href="/delete/strava_api.bin" class="btn btn-sm btn-danger">Remove credentials</a>
{% endif %}
{% if token %}
<a href="/delete/strava_token.txt" class="btn btn-sm btn-danger">Remove authorization</a>
{% elif cid and cs %}
<a href="{{ url_for('strava_auth') }}" class="btn btn-sm btn-secondary">Authorize</a>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-sm-6 col-md-5 top-buffer">
<form id="strava" action="{{ url_for('strava', username=username) }}" method="post">
<div class="row">
<div class="col-md-12">
<label class="col-form-label col-form-label-sm text-shadow">Client ID</label>
<input type="text" id="client_id" name="client_id" value="{{ cid }}" class="form-control form-control-sm">
</div>
<div class="col-md-12">
<label class="col-form-label col-form-label-sm text-shadow">Client secret</label>
<input type="text" id="client_secret" name="client_secret" value="{{ cs }}" class="form-control form-control-sm">
</div>
</div>
<div class="row">
<div class="col-md-12 top-buffer">
<input type="submit" value="Submit" class="btn btn-sm btn-light">
</div>
</div>
</form>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="list-group top-buffer">
{% for message in messages %}
<li class="list-group-item py-2">
<div class="text-shadow">{{ message }}</div>
</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
</div>
{% endblock %}
52 changes: 31 additions & 21 deletions zwift_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,6 @@
with warnings.catch_warnings():
from stravalib.client import Client

STRAVA_API_FILE = "%s/strava-api.txt" % STORAGE_DIR
if os.path.exists(STRAVA_API_FILE):
with open(STRAVA_API_FILE) as f:
STRAVA_CLIENT_ID = int(f.readline().rstrip('\r\n'))
STRAVA_CLIENT_SECRET = f.readline().rstrip('\r\n')

from tokens import *

# Android uses https for cdn
Expand Down Expand Up @@ -710,11 +704,27 @@ def reset(username):
return render_template("reset.html", username=current_user.username)


@app.route("/strava", methods=['GET'])
@app.route("/strava/<username>/", methods=["GET", "POST"])
@login_required
def strava(username):
profile_dir = '%s/%s' % (STORAGE_DIR, current_user.player_id)
api = '%s/strava_api.bin' % profile_dir
token = os.path.isfile('%s/strava_token.txt' % profile_dir)
if request.method == "POST":
if request.form['client_id'] == "" or request.form['client_secret'] == "":
flash("Client ID and secret can't be empty.")
return render_template("strava.html", username=current_user.username, token=token)
encrypt_credentials(api, (request.form['client_id'], request.form['client_secret']))
cred = decrypt_credentials(api)
return render_template("strava.html", username=current_user.username, cid=cred[0], cs=cred[1], token=token)


@app.route("/strava_auth", methods=['GET'])
@login_required
def strava():
def strava_auth():
cred = decrypt_credentials('%s/%s/strava_api.bin' % (STORAGE_DIR, current_user.player_id))
client = Client()
url = client.authorization_url(client_id=STRAVA_CLIENT_ID,
url = client.authorization_url(client_id=cred[0],
redirect_uri='https://launcher.zwift.com/authorization',
scope=['activity:write'])
return redirect(url)
Expand All @@ -723,21 +733,22 @@ def strava():
@app.route("/authorization", methods=["GET", "POST"])
@login_required
def authorization():
try:
try:
cred = decrypt_credentials('%s/%s/strava_api.bin' % (STORAGE_DIR, current_user.player_id))
client = Client()
code = request.args.get('code')
token_response = client.exchange_code_for_token(client_id=STRAVA_CLIENT_ID, client_secret=STRAVA_CLIENT_SECRET, code=code)
token_response = client.exchange_code_for_token(client_id=int(cred[0]), client_secret=cred[1], code=code)
with open(os.path.join(STORAGE_DIR, str(current_user.player_id), 'strava_token.txt'), 'w') as f:
f.write(str(STRAVA_CLIENT_ID) + '\n')
f.write(STRAVA_CLIENT_SECRET + '\n')
f.write(cred[0] + '\n')
f.write(cred[1] + '\n')
f.write(token_response['access_token'] + '\n')
f.write(token_response['refresh_token'] + '\n')
f.write(str(token_response['expires_at']) + '\n')
flash("Strava authorized.")
except Exception as exc:
logger.warning('Strava: %s' % repr(exc))
flash("Strava authorization canceled.")
return redirect(url_for('settings', username=current_user.username))
return redirect(url_for('strava', username=current_user.username))


def encrypt_credentials(file, cred):
Expand Down Expand Up @@ -945,9 +956,7 @@ def settings(username):
if os.path.isfile(achievements_file):
stat = os.stat(achievements_file)
achievements = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(stat.st_mtime))
api = os.path.isfile(STRAVA_API_FILE)
token = os.path.isfile(os.path.join(profile_dir, 'strava_token.txt'))
return render_template("settings.html", username=current_user.username, profile=profile, achievements=achievements, api=api, token=token)
return render_template("settings.html", username=current_user.username, profile=profile, achievements=achievements)


@app.route("/download/<filename>", methods=["GET"])
Expand All @@ -968,14 +977,15 @@ def download_avatarLarge(player_id):
@app.route("/delete/<filename>", methods=["GET"])
@login_required
def delete(filename):
player_id = current_user.player_id
credentials = ['garmin_credentials.bin', 'zwift_credentials.bin', 'intervals_credentials.bin']
if filename not in ['profile.bin', 'achievements.bin', 'strava_token.txt'] and filename not in credentials:
strava = ['strava_api.bin', 'strava_token.txt']
if filename not in ['profile.bin', 'achievements.bin'] + credentials + strava:
return '', 403
profile_dir = os.path.join(STORAGE_DIR, str(player_id))
delete_file = os.path.join(profile_dir, filename)
delete_file = os.path.join(STORAGE_DIR, str(current_user.player_id), filename)
if os.path.isfile(delete_file):
os.remove("%s" % delete_file)
if filename in strava:
return redirect(url_for('strava', username=current_user.username))
if filename in credentials:
flash("Credentials removed.")
return redirect(url_for('settings', username=current_user.username))
Expand Down

0 comments on commit 1152daa

Please sign in to comment.