Update backend/main.py
This commit is contained in:
parent
c0ccf050c7
commit
a4094f13cf
1 changed files with 114 additions and 0 deletions
114
backend/main.py
114
backend/main.py
|
|
@ -480,6 +480,120 @@ async def usage_stats() -> Dict:
|
|||
return usage
|
||||
|
||||
|
||||
# Auth state stored in memory (persists while container runs)
|
||||
_auth_process = None
|
||||
_auth_url = None
|
||||
_auth_status = "unknown" # unknown | logged_in | logged_out | pending
|
||||
|
||||
|
||||
async def _check_claude_auth() -> str:
|
||||
"""Check if claude is authenticated by running a quick no-op"""
|
||||
global _auth_status
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"claude", "--version",
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
await asyncio.wait_for(proc.communicate(), timeout=5)
|
||||
# Check for saved credentials file
|
||||
creds = Path("/root/.claude/.credentials.json")
|
||||
if creds.exists():
|
||||
import json as _json
|
||||
data = _json.loads(creds.read_text())
|
||||
if data:
|
||||
_auth_status = "logged_in"
|
||||
return "logged_in"
|
||||
_auth_status = "logged_out"
|
||||
return "logged_out"
|
||||
except Exception:
|
||||
_auth_status = "unknown"
|
||||
return "unknown"
|
||||
|
||||
|
||||
@app.get("/api/auth/status")
|
||||
async def auth_status():
|
||||
"""Check Claude auth status"""
|
||||
global _auth_status, _auth_url
|
||||
status = await _check_claude_auth()
|
||||
# Try to get account info if logged in
|
||||
account = None
|
||||
try:
|
||||
creds_file = Path("/root/.claude/.credentials.json")
|
||||
if creds_file.exists():
|
||||
import json as _json
|
||||
creds = _json.loads(creds_file.read_text())
|
||||
account = creds.get("account", {}).get("emailAddress") or creds.get("email")
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"status": status,
|
||||
"account": account,
|
||||
"auth_url": _auth_url if status == "pending" else None
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
async def auth_login():
|
||||
"""Initiate Claude OAuth login - returns the URL to open in browser"""
|
||||
global _auth_process, _auth_url, _auth_status
|
||||
_auth_status = "pending"
|
||||
_auth_url = None
|
||||
|
||||
try:
|
||||
# Run claude auth login and capture the URL it prints
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"claude", "auth", "login",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.DEVNULL
|
||||
)
|
||||
_auth_process = proc
|
||||
|
||||
# Read output lines looking for the OAuth URL (30s timeout)
|
||||
url = None
|
||||
try:
|
||||
async def _read_url():
|
||||
while True:
|
||||
line = await proc.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
text = line.decode(errors="replace")
|
||||
logger.info(f"claude auth: {text.strip()}")
|
||||
# Claude prints something like: "Open this URL: https://..."
|
||||
for part in text.split():
|
||||
if part.startswith("https://claude.ai") or part.startswith("https://anthropic"):
|
||||
return part.strip()
|
||||
return None
|
||||
url = await asyncio.wait_for(_read_url(), timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
if url:
|
||||
_auth_url = url
|
||||
return {"status": "pending", "auth_url": url, "message": "Open this URL in your browser to log in"}
|
||||
else:
|
||||
return {"status": "error", "message": "Could not extract login URL from claude CLI. Try running 'claude auth login' manually in the container."}
|
||||
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
@app.post("/api/auth/logout")
|
||||
async def auth_logout():
|
||||
"""Log out of Claude"""
|
||||
global _auth_status
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"claude", "auth", "logout",
|
||||
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
await asyncio.wait_for(proc.communicate(), timeout=10)
|
||||
_auth_status = "logged_out"
|
||||
return {"status": "logged_out"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
# Serve static frontend
|
||||
app.mount("/", StaticFiles(directory="/app/frontend/dist", html=True), name="static")
|
||||
|
||||
|
|
|
|||
Reference in a new issue