62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from dataclasses import dataclass
|
|
|
|
import aiohttp
|
|
|
|
|
|
@dataclass
|
|
class OAuth2Token:
|
|
access_token: str
|
|
refresh_token: str
|
|
expires_at: float
|
|
token_type: str = "Bearer"
|
|
|
|
@property
|
|
def is_expired(self) -> bool:
|
|
return time.time() > self.expires_at - 60
|
|
|
|
|
|
class EmailOAuth2Manager:
|
|
MICROSOFT_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
|
|
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
|
|
|
|
def __init__(self, provider: str, client_id: str, client_secret: str):
|
|
self._provider = provider
|
|
self._client_id = client_id
|
|
self._client_secret = client_secret
|
|
self._token: OAuth2Token | None = None
|
|
|
|
def _token_url(self) -> str:
|
|
if self._provider in ("outlook", "office365", "hotmail", "live"):
|
|
return self.MICROSOFT_TOKEN_URL
|
|
return self.GOOGLE_TOKEN_URL
|
|
|
|
async def get_access_token(self, refresh_token: str) -> str:
|
|
if self._token and not self._token.is_expired:
|
|
return self._token.access_token
|
|
|
|
data = {
|
|
"client_id": self._client_id,
|
|
"client_secret": self._client_secret,
|
|
"refresh_token": refresh_token,
|
|
"grant_type": "refresh_token",
|
|
}
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.post(
|
|
self._token_url(), data=data, timeout=aiohttp.ClientTimeout(total=15)
|
|
) as resp:
|
|
if resp.status != 200:
|
|
raise RuntimeError(f"OAuth2 token refresh failed: {await resp.text()}")
|
|
token_data = await resp.json()
|
|
self._token = OAuth2Token(
|
|
access_token=token_data["access_token"],
|
|
refresh_token=token_data.get("refresh_token", refresh_token),
|
|
expires_at=time.time() + token_data.get("expires_in", 3600),
|
|
)
|
|
return self._token.access_token
|
|
|
|
def build_xoauth2_string(self, username: str, access_token: str) -> str:
|
|
return f"user={username}\x01auth=Bearer {access_token}\x01\x01"
|