|
| 1 | +from datetime import UTC, datetime, timedelta |
| 2 | + |
| 3 | +from pydantic import BaseModel |
| 4 | + |
| 5 | +from app.configs import config |
| 6 | +from app.extensions.ext_database import db |
| 7 | +from app.extensions.ext_redis import redis_client |
| 8 | +from app.libs.helper import naive_utc_now |
| 9 | +from app.models.account import ( |
| 10 | + Account, |
| 11 | + AccountStatus, |
| 12 | +) |
| 13 | +from app.services.passport import PassportService |
| 14 | +from app.services.token import TokenService |
| 15 | + |
| 16 | + |
| 17 | +class TokenPair(BaseModel): |
| 18 | + access_token: str |
| 19 | + refresh_token: str |
| 20 | + csrf_token: str |
| 21 | + |
| 22 | + |
| 23 | +class AccountService: |
| 24 | + @staticmethod |
| 25 | + def _get_refresh_token_key(refresh_token: str) -> str: |
| 26 | + return f"{config.REFRESH_TOKEN_PREFIX}{refresh_token}" |
| 27 | + |
| 28 | + @staticmethod |
| 29 | + def _get_account_refresh_token_key(account_id: str) -> str: |
| 30 | + return f"{config.ACCOUNT_REFRESH_TOKEN_PREFIX}{account_id}" |
| 31 | + |
| 32 | + @staticmethod |
| 33 | + def store_refresh_token(refresh_token: str, account_id: str): |
| 34 | + redis_client.setex(AccountService._get_refresh_token_key(refresh_token), config.REFRESH_TOKEN_EXPIRE_DAYS, |
| 35 | + account_id) |
| 36 | + redis_client.setex( |
| 37 | + AccountService._get_account_refresh_token_key(account_id), config.REFRESH_TOKEN_EXPIRE_DAYS, refresh_token |
| 38 | + ) |
| 39 | + |
| 40 | + @staticmethod |
| 41 | + def get_account_jwt_token(account: Account) -> str: |
| 42 | + exp_dt = datetime.now(UTC) + timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES) |
| 43 | + exp = int(exp_dt.timestamp()) |
| 44 | + payload = { |
| 45 | + "user_id": account.id, |
| 46 | + "exp": exp, |
| 47 | + "iss": config.EDITION, |
| 48 | + "sub": "Console API Passport", |
| 49 | + } |
| 50 | + |
| 51 | + token: str = PassportService().issue(payload) |
| 52 | + return token |
| 53 | + |
| 54 | + @staticmethod |
| 55 | + def update_login_info(account: Account, *, ip_address: str): |
| 56 | + account.last_login_at = naive_utc_now() |
| 57 | + account.last_login_ip = ip_address |
| 58 | + db.session.add(account) |
| 59 | + db.session.commit() |
| 60 | + |
| 61 | + @staticmethod |
| 62 | + def login(account: Account, ip_address: str | None = None) -> TokenPair: |
| 63 | + if ip_address: |
| 64 | + AccountService.update_login_info(account=account, ip_address=ip_address) |
| 65 | + |
| 66 | + if account.status == AccountStatus.PENDING: |
| 67 | + account.status = AccountStatus.ACTIVE |
| 68 | + db.session.commit() |
| 69 | + |
| 70 | + access_token = AccountService.get_account_jwt_token(account=account) |
| 71 | + refresh_token = TokenService().generate_refresh_token() |
| 72 | + csrf_token = TokenService().generate_csrf_token(account.id) |
| 73 | + |
| 74 | + AccountService.store_refresh_token(refresh_token, account.id) |
| 75 | + |
| 76 | + return TokenPair(access_token=access_token, refresh_token=refresh_token, csrf_token=csrf_token) |
0 commit comments