diff --git a/api/v1/auth.py b/api/v1/auth.py index 7c2d1a4..094d8b8 100644 --- a/api/v1/auth.py +++ b/api/v1/auth.py @@ -152,16 +152,47 @@ async def login_submit(request: Request): # --------------------------------------------------------------------------- -# GET /auth/status — check which services are linked +# GET /auth/status — get all linked services for a Discord user # --------------------------------------------------------------------------- -@router.get("/status") +@router.get("/Discord/status") async def auth_status(discord_id: int): - """Return which services this Discord user has linked.""" - services: dict[str, bool] = {} - for svc_name in list_auth_services(): - services[svc_name] = auth_store.is_authenticated(discord_id, svc_name) - return {"discord_id": discord_id, "services": services} + """ + Return all services linked to this Discord user with full details. + + Response: + { + "discord_id": 123456789, + "linked_services": { + "jellyfin": { + "external_user_id": "abc123", + "external_name": "Tim", + "linked_at": "2026-05-25T10:00:00", + "credentials": { + "token": "jwt...", + "url": "http://jellyfin:8096", + "user_id": "abc123" + } + } + } + } + """ + auths = auth_store.get_all_auths(discord_id) + + linked_services: dict[str, dict] = {} + for auth in auths: + svc_name = auth["service"] + linked_services[svc_name] = { + "external_user_id": auth["external_user_id"], + "external_name": auth["external_name"], + "linked_at": auth["linked_at"], + "credentials": auth["credentials"], + } + + return { + "discord_id": discord_id, + "linked_services": linked_services, + } # --------------------------------------------------------------------------- diff --git a/core/auth_store.py b/core/auth_store.py index cb57cb4..e6f8d13 100644 --- a/core/auth_store.py +++ b/core/auth_store.py @@ -298,6 +298,48 @@ def revoke(discord_user_id: int, service: str) -> None: logger.info("Revoked auth for user %s on %s", discord_user_id, service) +def get_all_auths(discord_user_id: int) -> list[dict]: + """ + Return all active auth records for a Discord user. + Each record includes service name, external user id, external name, + linked_at timestamp, and the raw credentials (e.g. Jellyfin token + URL). + + Used by the /api/v1/auth/status endpoint so other services can discover + linked accounts for a given Discord ID. + """ + _ensure_schema() + import json + + with _db_lock: + conn = _get_conn() + rows = conn.execute( + """SELECT service, external_user_id, external_name, credentials, linked_at + FROM user_auth + WHERE discord_user_id = ? AND is_active = 1 + ORDER BY linked_at DESC""", + (discord_user_id,), + ).fetchall() + conn.close() + + results: list[dict] = [] + for row in rows: + creds = {} + if row["credentials"]: + try: + creds = json.loads(row["credentials"]) + except (json.JSONDecodeError, TypeError): + creds = {} + results.append({ + "service": row["service"], + "external_user_id": row["external_user_id"] or "", + "external_name": row["external_name"] or "", + "linked_at": row["linked_at"] or "", + "credentials": creds, + }) + + return results + + # --------------------------------------------------------------------------- # Dev / testing — reset the entire store # ---------------------------------------------------------------------------