# app.py # running: # $ cd /var/sites/api.lockstep.at # $ source .venv/bin/activate # $ source .env # $ flask --app api run --port 8000 # Authomatic 1.3.0 is not compatible with Python 3.12+ # patch: /var/sites/api.lockstep.at/.venv/lib/python3.13/site-packages/authomatic/providers/__init__.py # def _fetch() # HTTPSConnection( # # cert_file=cert_file, # <-- comment this # sync continuously: # $ while sleep 1; do diff -q api.py /tmp/api.py; if [ $? -ne 0 ]; then scp api.py lockstep@api.lockstep.at:/var/sites/api.lockstep.at/; cp api.py /tmp/api.py; fi; done import os from flask import Flask, request, session, jsonify, make_response from werkzeug.middleware.proxy_fix import ProxyFix from authomatic import Authomatic from authomatic.adapters import WerkzeugAdapter from authomatic.providers import oauth2 from authomatic.providers import PROVIDER_ID_MAP as AUTHOMATIC_PROVIDER_ID_MAP class Spotify(oauth2.OAuth2): """ Custom Authomatic provider for Spotify Web API Authorization Code Flow. Spotify docs: - Authorization endpoint: https://accounts.spotify.com/authorize - Token endpoint: https://accounts.spotify.com/api/token - Current user profile: https://api.spotify.com/v1/me """ name = "spotify" # Provider endpoints user_authorization_url = "https://accounts.spotify.com/authorize" access_token_url = "https://accounts.spotify.com/api/token" user_info_url = "https://api.spotify.com/v1/me" # Helpful preset scopes spotify_scopes = ["playlist-read-private"] def update_user(self): """ Fetch Spotify profile and map it onto Authomatic's generic user object. """ response = self.access(self.user_info_url) if response.status != 200: return response data = response.data or {} # Common Authomatic user fields self.user.id = data.get("id") self.user.name = data.get("display_name") or data.get("id") #self.user.email = data.get("email") # Spotify has images as a list; use the first one if present. images = data.get("images") or [] if images: self.user.picture = images[0].get("url") # Optional extra fields if your Authomatic version exposes them self.user.username = data.get("id") self.user.link = (data.get("external_urls") or {}).get("spotify") return response # "must exist in the same module as Spotify (Authomatic provider class)" 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. REDIRECT_URI = os.environ.get( "SPOTIFY_REDIRECT_URI", #"https://api.lockstep.at/spotify/callback" "https://api.lockstep.at/login/spotify/" ) app = Flask(__name__) app.secret_key = os.environ["FLASK_SECRET_KEY"] app.wsgi_app = ProxyFix( app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 ) CONFIG = { "spotify": { "class_": Spotify, "consumer_key": SPOTIFY_CLIENT_ID, "consumer_secret": SPOTIFY_CLIENT_SECRET, # Spotify scopes are space-separated conceptually; Authomatic accepts a list. "scope": Spotify.spotify_scopes # Optional: force Spotify to show consent every time. # "user_authorization_params": {"show_dialog": "true"}, } } authomatic = Authomatic(CONFIG, app.secret_key) @app.route("/") def index(): return """
After login, this app fetches your Spotify profile and one extra API call.
""" @app.route("/login/