- New auto_login_service.py: stores encrypted credentials (login ID, password, TOTP secret), performs headless Zerodha login via pyotp, and refreshes the session daily at 6:05 AM IST via background thread - New auto_login router: setup, status, remove, and manual trigger endpoints - Scheduler started at app boot alongside existing daemons - Added pyotp==2.9.0 dependency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
from fastapi import APIRouter, HTTPException, Request
|
|
from pydantic import BaseModel
|
|
|
|
from app.services.auth_service import get_user_for_session
|
|
from app.services.auto_login_service import (
|
|
delete_auto_login_credentials,
|
|
execute_auto_login,
|
|
get_auto_login_status,
|
|
save_auto_login_credentials,
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/auto-login")
|
|
|
|
|
|
def _require_user(request: Request):
|
|
session_id = request.cookies.get("session_id")
|
|
if not session_id:
|
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
user = get_user_for_session(session_id)
|
|
if not user:
|
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
return user
|
|
|
|
|
|
class AutoLoginSetupRequest(BaseModel):
|
|
zerodha_login_id: str
|
|
password: str
|
|
totp_secret: str
|
|
|
|
|
|
@router.post("/setup")
|
|
async def setup_auto_login(payload: AutoLoginSetupRequest, request: Request):
|
|
user = _require_user(request)
|
|
|
|
if not payload.zerodha_login_id.strip():
|
|
raise HTTPException(status_code=400, detail="Zerodha login ID is required")
|
|
if not payload.password:
|
|
raise HTTPException(status_code=400, detail="Password is required")
|
|
totp_clean = payload.totp_secret.strip().replace(" ", "")
|
|
if len(totp_clean) < 16:
|
|
raise HTTPException(status_code=400, detail="TOTP secret must be at least 16 characters")
|
|
|
|
save_auto_login_credentials(
|
|
user_id=user["id"],
|
|
zerodha_login_id=payload.zerodha_login_id.strip(),
|
|
password=payload.password,
|
|
totp_secret=totp_clean,
|
|
)
|
|
|
|
# Immediately test the credentials with a live login attempt
|
|
result = execute_auto_login(user_id=user["id"], email=user["username"])
|
|
if not result["success"]:
|
|
# Roll back — bad credentials shouldn't be saved
|
|
delete_auto_login_credentials(user["id"])
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Credentials saved but login test failed: {result.get('error', 'Unknown error')}. "
|
|
"Please check your Zerodha login ID, password, and TOTP secret.",
|
|
)
|
|
|
|
return {"configured": True, "message": "Auto-login set up and verified successfully"}
|
|
|
|
|
|
@router.get("/status")
|
|
async def auto_login_status(request: Request):
|
|
user = _require_user(request)
|
|
return get_auto_login_status(user["id"])
|
|
|
|
|
|
@router.delete("/setup")
|
|
async def remove_auto_login(request: Request):
|
|
user = _require_user(request)
|
|
delete_auto_login_credentials(user["id"])
|
|
return {"configured": False, "message": "Auto-login credentials removed"}
|
|
|
|
|
|
@router.post("/trigger")
|
|
async def trigger_auto_login(request: Request):
|
|
"""Manually trigger an immediate token refresh."""
|
|
user = _require_user(request)
|
|
status = get_auto_login_status(user["id"])
|
|
if not status.get("configured"):
|
|
raise HTTPException(status_code=400, detail="Auto-login is not configured")
|
|
result = execute_auto_login(user_id=user["id"], email=user["username"])
|
|
if not result["success"]:
|
|
raise HTTPException(status_code=502, detail=result.get("error", "Auto-login failed"))
|
|
return {"success": True, "message": "Zerodha session refreshed successfully"}
|