feat: allow redirect_uri param to spotify login, for Lockstep Demo app flow
This commit is contained in:
59
api.py
59
api.py
@@ -21,8 +21,9 @@ import sqlite3
|
||||
from base64 import b64encode
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import json
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from flask import Flask, request, session, jsonify, make_response
|
||||
from flask import Flask, request, session, jsonify, make_response, redirect, url_for
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
from authomatic import Authomatic
|
||||
from authomatic.adapters import WerkzeugAdapter
|
||||
@@ -93,13 +94,22 @@ PROVIDER_ID_MAP = list(AUTHOMATIC_PROVIDER_ID_MAP) + [Spotify]
|
||||
SPOTIFY_CLIENT_ID = os.environ["SPOTIFY_CLIENT_ID"]
|
||||
SPOTIFY_CLIENT_SECRET = os.environ["SPOTIFY_CLIENT_SECRET"]
|
||||
|
||||
# Must exactly match a Redirect URI configured in your Spotify app settings.
|
||||
# OAuth redirect registered in the Spotify Developer Dashboard (Authorization Code
|
||||
# flow). Spotify sends the browser here with ?code=&state= — not to the Android app scheme.
|
||||
REDIRECT_URI = os.environ.get(
|
||||
"SPOTIFY_REDIRECT_URI",
|
||||
#"https://api.lockstep.at/spotify/callback"
|
||||
"https://api.lockstep.at/login/spotify/"
|
||||
)
|
||||
|
||||
# After server-side token exchange, the browser may be redirected to this URL with
|
||||
# tokens in the query string (mobile / Custom Tab). This is NOT registered with Spotify;
|
||||
# only REDIRECT_URI above goes in the dashboard for this flow.
|
||||
ALLOWED_SPOTIFY_APP_POST_LOGIN_REDIRECT = os.environ.get(
|
||||
"SPOTIFY_APP_POST_LOGIN_REDIRECT_URI",
|
||||
"at.lockstep.player://spotify/callback",
|
||||
)
|
||||
|
||||
DB_PATH = os.environ.get("TOKEN_DB_PATH", "spotify_tokens.db")
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -391,6 +401,28 @@ def index():
|
||||
|
||||
@app.route("/login/<provider_name>/", methods=["GET", "POST"])
|
||||
def login(provider_name):
|
||||
# Authomatic 1.3.0 (oauth2.login) only runs "phase 1" — redirect to Spotify —
|
||||
# when there are no query parameters, or only ``user_state``:
|
||||
# elif (not self.params or (len(self.params) == 1 and 'user_state' in self.params))
|
||||
# A mobile client opening ``/login/spotify/?redirect_uri=...`` therefore matches
|
||||
# no branch; login() returns without calling redirect() → empty body and HTTP 200
|
||||
# (white page). Stash the app callback in the session and reload without query args.
|
||||
if (
|
||||
provider_name == "spotify"
|
||||
and request.args.get("redirect_uri")
|
||||
and "code" not in request.args
|
||||
and "error" not in request.args
|
||||
):
|
||||
requested = request.args.get("redirect_uri", "")
|
||||
if requested != ALLOWED_SPOTIFY_APP_POST_LOGIN_REDIRECT:
|
||||
return jsonify({
|
||||
"ok": False,
|
||||
"error": "redirect_uri not allowed for this client",
|
||||
}), 400
|
||||
session["spotify_oauth_app_redirect_uri"] = requested
|
||||
session.modified = True
|
||||
return redirect(url_for("login", provider_name=provider_name), code=302)
|
||||
|
||||
response = make_response()
|
||||
|
||||
# Let Authomatic handle the OAuth2 handshake.
|
||||
@@ -398,7 +430,7 @@ def login(provider_name):
|
||||
WerkzeugAdapter(request, response),
|
||||
provider_name,
|
||||
session=session,
|
||||
session_saver=lambda: session.modified
|
||||
session_saver=lambda: setattr(session, "modified", True),
|
||||
)
|
||||
|
||||
# If result is None, Authomatic is still redirecting/processing.
|
||||
@@ -469,6 +501,27 @@ def login(provider_name):
|
||||
# keep the Spotify user id in session
|
||||
session["spotify_user_id"] = result.user.id
|
||||
|
||||
app_redirect = session.pop("spotify_oauth_app_redirect_uri", None)
|
||||
if app_redirect:
|
||||
if app_redirect != ALLOWED_SPOTIFY_APP_POST_LOGIN_REDIRECT:
|
||||
return jsonify({
|
||||
"ok": False,
|
||||
"error": "Stored app redirect_uri does not match allowlist",
|
||||
}), 400
|
||||
sep = "&" if ("?" in app_redirect) else "?"
|
||||
target = (
|
||||
f"{app_redirect}{sep}"
|
||||
+ urlencode(
|
||||
{
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"expires_in": str(expires_in),
|
||||
"token_type": token_type or "",
|
||||
},
|
||||
)
|
||||
)
|
||||
return redirect(target, code=302)
|
||||
|
||||
return jsonify({
|
||||
"ok": True,
|
||||
"spotify_user_id": result.user.id,
|
||||
|
||||
Reference in New Issue
Block a user