Add artdag to OAuth clients + POST /auth/oauth/token endpoint
Standard HTTP token exchange for clients that don't share the coop DB. Returns user_id, username, display_name, grant_token in exchange for a valid authorization code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,7 @@ from .services import (
|
|||||||
SESSION_USER_KEY = "uid"
|
SESSION_USER_KEY = "uid"
|
||||||
ACCOUNT_SESSION_KEY = "account_sid"
|
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"):
|
def register(url_prefix="/auth"):
|
||||||
@@ -121,6 +121,69 @@ def register(url_prefix="/auth"):
|
|||||||
f"&account_did={account_did}"
|
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) ------------------------------
|
# --- Grant verification (internal endpoint) ------------------------------
|
||||||
|
|
||||||
@auth_bp.get("/internal/verify-grant")
|
@auth_bp.get("/internal/verify-grant")
|
||||||
|
|||||||
Reference in New Issue
Block a user