diff --git a/bp/auth/routes.py b/bp/auth/routes.py index ce04927..7e40820 100644 --- a/bp/auth/routes.py +++ b/bp/auth/routes.py @@ -43,7 +43,7 @@ from .services import ( SESSION_USER_KEY = "uid" ACCOUNT_SESSION_KEY = "account_sid" -ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "federation"} +ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "federation", "artdag"} def register(url_prefix="/auth"): @@ -121,6 +121,69 @@ def register(url_prefix="/auth"): f"&account_did={account_did}" ) + # --- OAuth2 token exchange (for external clients like artdag) ------------- + + @auth_bp.post("/oauth/token") + @auth_bp.post("/oauth/token/") + async def oauth_token(): + """Exchange an authorization code for user info + grant token. + + Used by clients that don't share the coop database (e.g. artdag). + Accepts JSON: {code, client_id, redirect_uri} + Returns JSON: {user_id, username, display_name, grant_token} + """ + data = await request.get_json() + if not data: + return jsonify({"error": "invalid_request"}), 400 + + code = data.get("code", "") + client_id = data.get("client_id", "") + redirect_uri = data.get("redirect_uri", "") + + if client_id not in ALLOWED_CLIENTS: + return jsonify({"error": "invalid_client"}), 400 + + now = datetime.now(timezone.utc) + + async with get_session() as s: + async with s.begin(): + result = await s.execute( + select(OAuthCode) + .where(OAuthCode.code == code) + .with_for_update() + ) + oauth_code = result.scalar_one_or_none() + + if not oauth_code: + return jsonify({"error": "invalid_grant"}), 400 + + if oauth_code.used_at is not None: + return jsonify({"error": "invalid_grant"}), 400 + + if oauth_code.expires_at < now: + return jsonify({"error": "invalid_grant"}), 400 + + if oauth_code.client_id != client_id: + return jsonify({"error": "invalid_grant"}), 400 + + if oauth_code.redirect_uri != redirect_uri: + return jsonify({"error": "invalid_grant"}), 400 + + oauth_code.used_at = now + user_id = oauth_code.user_id + grant_token = oauth_code.grant_token + + user = await s.get(User, user_id) + if not user: + return jsonify({"error": "invalid_grant"}), 400 + + return jsonify({ + "user_id": user_id, + "username": user.email or "", + "display_name": user.name or "", + "grant_token": grant_token, + }) + # --- Grant verification (internal endpoint) ------------------------------ @auth_bp.get("/internal/verify-grant")