import functools from urllib.parse import urlparse, urlunparse import jwt from authing import AuthenticationClient from fastapi import Request, Response from fastapi.responses import RedirectResponse from exceptions import InvalidCode, InvalidCredentials, InvalidToken from .base import BaseAuthPlugin class PingCAPAuthenticationClient: def __init__(self, auth_app_id, auth_app_host, auth_app_secret, redirect_uri=None): super(PingCAPAuthenticationClient, self).__init__() self.client = AuthenticationClient( auth_app_id, auth_app_host, auth_app_secret, redirect_uri=redirect_uri, ) def build_authorize_url(self, scope: str, state: str) -> str: return self.client.build_authorize_url(scope=scope, state=state) def get_token_by_code(self, code: str) -> str: r = self.client.get_access_token_by_code(code) if "access_token" not in r: raise InvalidCode(code) return jwt.encode({"access_token": r["access_token"]}, "") def get_token_by_credentials( self, scope: str, access_key: str, secret_key: str ) -> str: r = self.client.get_access_token_by_client_credentials( scope, access_key, secret_key ) if "access_token" not in r: raise InvalidCredentials(access_key, secret_key) # the access_token is not useful in this case return jwt.encode( { "access_key": access_key, }, "", ) def get_user_identity_by_token(self, token: str) -> str: payload = jwt.decode(token, options={"verify_signature": False}) if "access_key" in payload: return payload["access_key"] if "access_token" in payload: user_info = self.client.get_user_info_by_access_token( payload["access_token"] ) return user_info["profile"] raise InvalidToken(token) class PingCAPAuthPlugin(BaseAuthPlugin): def __init__( self, auth_app_host: str, auth_app_id: str, auth_app_secret: str, ): self.auth_app_host = auth_app_host self.auth_app_id = auth_app_id self.auth_app_secret = auth_app_secret def build_authorize_url(self, request: Request) -> str: referer = request.headers.get("referer") if not referer: return None r = urlparse(referer) redirect_uri = urlunparse( (r.scheme, r.netloc, "/langlink-api/login", None, None, None) ) auth_client = PingCAPAuthenticationClient( self.auth_app_id, self.auth_app_host, self.auth_app_secret, redirect_uri=redirect_uri, ) return auth_client.build_authorize_url(scope="profile openid", state=referer) def login(self, request: Request) -> Response: state = request.query_params["state"] r = urlparse(state) auth_client = PingCAPAuthenticationClient( self.auth_app_id, self.auth_app_host, self.auth_app_secret, redirect_uri=urlunparse( (r.scheme, r.netloc, "/langlink-api/login", None, None, None) ), ) code = request.query_params.get("code") if not code: return {"status": "error", "message": "no code in query params"}, 400 token = auth_client.get_token_by_code(code) user = auth_client.get_user_identity_by_token(token) response = RedirectResponse(url=state) response.set_cookie( key="token", value=jwt.encode({"user": user}, self.auth_app_secret, algorithm="HS256"), ) return response def get_user_identity(self, request: Request) -> str: user = request.headers.get("x-langlink-user") access_key = request.headers.get("x-langlink-access-key") access_secret = request.headers.get("x-langlink-access-secret") if user and self.check_credentials(access_key, access_secret): return user token = request.cookies.get("token") if not token: return None try: credentials = jwt.decode(token, self.auth_app_secret, algorithms=["HS256"]) return credentials["user"] except: return None @functools.lru_cache def check_credentials(self, access_key: str, access_secret: str) -> bool: auth_client = PingCAPAuthenticationClient( self.auth_app_id, self.auth_app_host, self.auth_app_secret, ) try: auth_client.get_token_by_credentials( "profile openid", access_key, access_secret ) return True except Exception as e: return False