"""Spotify login workflow independent of HTTP routing.""" import secrets import httpx2 from app.adapters.spotify.auth import ( build_authorize_url, derive_code_challenge, exchange_authorization_code, generate_code_verifier, ) from app.adapters.spotify.client import SpotifyClient from app.adapters.spotify.errors import SpotifyError from app.adapters.spotify.session import PendingLogins, SessionStore, SpotifySession from app.config import Settings def begin_login(settings: Settings, pending_logins: PendingLogins) -> str: """Store a pending PKCE login and return its Spotify authorization URL.""" state = secrets.token_urlsafe(32) code_verifier = generate_code_verifier() pending_logins.add(state, code_verifier) return build_authorize_url( settings.spotify_client_id, settings.spotify_redirect_uri, state, derive_code_challenge(code_verifier), ) async def complete_login( http: httpx2.AsyncClient, settings: Settings, pending_logins: PendingLogins, session_store: SessionStore, code: str, state: str, ) -> str | None: """Complete a pending Spotify login and return its application session ID.""" code_verifier = pending_logins.pop(state) if code_verifier is None: return None try: tokens = await exchange_authorization_code( http, client_id=settings.spotify_client_id, redirect_uri=settings.spotify_redirect_uri, code=code, code_verifier=code_verifier, ) bootstrap_session = SpotifySession(tokens=tokens, account_id="", display_name="") current_user = await SpotifyClient( http, bootstrap_session, settings, ).fetch_current_user() session = SpotifySession( tokens=bootstrap_session.tokens, account_id=current_user.account_id, display_name=current_user.display_name, ) return session_store.create(session) except (SpotifyError, ValueError): return None