login.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import logging
  2. import time
  3. from typing import Any
  4. import httpx
  5. import typer
  6. from pydantic import BaseModel
  7. from fastapi_cloud_cli.config import Settings
  8. from fastapi_cloud_cli.utils.api import APIClient
  9. from fastapi_cloud_cli.utils.auth import AuthConfig, write_auth_config
  10. from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
  11. logger = logging.getLogger(__name__)
  12. class AuthorizationData(BaseModel):
  13. user_code: str
  14. device_code: str
  15. verification_uri: str
  16. verification_uri_complete: str
  17. interval: int = 5
  18. class TokenResponse(BaseModel):
  19. access_token: str
  20. def _start_device_authorization(
  21. client: httpx.Client,
  22. ) -> AuthorizationData:
  23. settings = Settings.get()
  24. response = client.post(
  25. "/login/device/authorization", data={"client_id": settings.client_id}
  26. )
  27. response.raise_for_status()
  28. return AuthorizationData.model_validate(response.json())
  29. def _fetch_access_token(client: httpx.Client, device_code: str, interval: int) -> str:
  30. settings = Settings.get()
  31. while True:
  32. response = client.post(
  33. "/login/device/token",
  34. data={
  35. "device_code": device_code,
  36. "client_id": settings.client_id,
  37. "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
  38. },
  39. )
  40. if response.status_code not in (200, 400):
  41. response.raise_for_status()
  42. if response.status_code == 400:
  43. data = response.json()
  44. if data.get("error") != "authorization_pending":
  45. response.raise_for_status()
  46. if response.status_code == 200:
  47. break
  48. time.sleep(interval)
  49. response_data = TokenResponse.model_validate(response.json())
  50. return response_data.access_token
  51. def login() -> Any:
  52. """
  53. Login to FastAPI Cloud. 🚀
  54. """
  55. with get_rich_toolkit() as toolkit, APIClient() as client:
  56. toolkit.print_title("Login to FastAPI Cloud", tag="FastAPI")
  57. toolkit.print_line()
  58. with toolkit.progress("Starting authorization") as progress:
  59. with handle_http_errors(progress):
  60. authorization_data = _start_device_authorization(client)
  61. url = authorization_data.verification_uri_complete
  62. progress.log(f"Opening [link={url}]{url}[/link]")
  63. toolkit.print_line()
  64. with toolkit.progress("Waiting for user to authorize...") as progress:
  65. typer.launch(url)
  66. with handle_http_errors(progress):
  67. access_token = _fetch_access_token(
  68. client, authorization_data.device_code, authorization_data.interval
  69. )
  70. write_auth_config(AuthConfig(access_token=access_token))
  71. progress.log("Now you are logged in! 🚀")