diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dab31e0..da08191 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: end-of-file-fixer - id: check-yaml - id: check-toml - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.2.0 + rev: v0.5.0 hooks: - id: ruff args: @@ -15,6 +15,6 @@ repos: - id: ruff-format - repo: https://github.com/pdm-project/pdm - rev: 2.12.3 + rev: 2.16.1 hooks: - id: pdm-lock-check diff --git a/README.md b/README.md index 7453d8d..5748c94 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,25 @@ ![GitHub release (with filter)](https://img.shields.io/github/v/release/fastapi-practices/fastapi_oauth20) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) - 在 FastAPI 中异步授权 OAuth2 客户端 -我们的目标是集成多个 CN 第三方客户端,敬请期待 +我们的目标是集成多个 CN 第三方客户端,敬请期待(🐦)... #### TODO: -如果我们能够很容易获取测试客户端,对接将会很快发生 +如果我们能够很容易获取测试客户端,或许这会很快 -- [ ] library tests -- [x] Google +- [ ] tests +- [x] [Google](https://developers.google.cn/identity/protocols/oauth2/javascript-implicit-flow?hl=en) - [ ] 微信 - [ ] QQ - [ ] 抖音 -- [x] 飞书 +- [x] [飞书](https://open.feishu.cn/document/common-capabilities/sso/web-application-sso/web-app-overview) - [ ] 钉钉 - [ ] 微博 - [ ] 百度 -- [x] Gitee -- [x] Github -- [X] 开源中国 +- [x] [Gitee](https://gitee.com/api/v5/oauth_doc#/) +- [x] [Github](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app) +- [X] [开源中国](https://www.oschina.net/openapi) - [ ] 阿里云 +- [ ] [Linux Do](https://connect.linux.do/) diff --git a/example/main.py b/example/main.py index e59d323..f214c2f 100644 --- a/example/main.py +++ b/example/main.py @@ -11,7 +11,7 @@ GOOGLE_CLIENT_ID = '1053650337583-ljnla4m1e5cg16erq3tld5vjflqh4bij.apps.googleusercontent.com' GOOGLE_CLIENT_SECRET = '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' -GOOGLE_REDIRECT_URI = 'http://localhost:8000/auth/google' +GOOGLE_REDIRECT_URI = 'http://localhost:8000/auth2/google' google_client = GoogleOAuth20(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET) oauth20 = FastAPIOAuth20(google_client, GOOGLE_REDIRECT_URI) @@ -22,9 +22,9 @@ async def login_google(): return await google_client.get_authorization_url(redirect_uri=GOOGLE_REDIRECT_URI) -@app.get('/auth/google', response_model=None) -async def auth_google(oauth: oauth20 = Depends()): - token, state = oauth +@app.get('/auth2/google', response_model=None) +async def auth2_google(oauth2: oauth20 = Depends()): + token, state = oauth2 access_token = token['access_token'] print(access_token) # do something diff --git a/fastapi_oauth20/__init__.py b/fastapi_oauth20/__init__.py index b939e42..830e902 100644 --- a/fastapi_oauth20/__init__.py +++ b/fastapi_oauth20/__init__.py @@ -1,12 +1,21 @@ """在 FastAPI 中异步授权 OAuth2 客户端""" -__version__ = '0.0.1a1' +__version__ = '0.0.1a2' -__all__ = ['OSChinaOAuth20', 'GoogleOAuth20', 'FeiShuOAuth20', 'GiteeOAuth20', 'GitHubOAuth20', 'FastAPIOAuth20'] +__all__ = [ + 'OSChinaOAuth20', + 'GoogleOAuth20', + 'FeiShuOAuth20', + 'GiteeOAuth20', + 'GitHubOAuth20', + 'FastAPIOAuth20', + 'LinuxDoOAuth20', +] from .clients.feishu import FeiShuOAuth20 from .clients.gitee import GiteeOAuth20 from .clients.github import GitHubOAuth20 from .clients.google import GoogleOAuth20 +from .clients.linuxdo import LinuxDoOAuth20 from .clients.oschina import OSChinaOAuth20 from .integrations.fastapi import OAuth20 as FastAPIOAuth20 diff --git a/fastapi_oauth20/clients/feishu.py b/fastapi_oauth20/clients/feishu.py index 83262ab..c6633ae 100644 --- a/fastapi_oauth20/clients/feishu.py +++ b/fastapi_oauth20/clients/feishu.py @@ -7,6 +7,7 @@ AUTHORIZE_ENDPOINT = 'https://passport.feishu.cn/suite/passport/oauth/authorize' ACCESS_TOKEN_ENDPOINT = 'https://passport.feishu.cn/suite/passport/oauth/token' REFRESH_TOKEN_ENDPOINT = AUTHORIZE_ENDPOINT +REVOKE_TOKEN_ENDPOINT = None DEFAULT_SCOPES = ['contact:user.employee_id:readonly', 'contact:user.base:readonly', 'contact:user.email:readonly'] PROFILE_ENDPOINT = 'https://passport.feishu.cn/suite/passport/oauth/userinfo' @@ -19,7 +20,7 @@ def __init__(self, client_id: str, client_secret: str): authorize_endpoint=AUTHORIZE_ENDPOINT, access_token_endpoint=ACCESS_TOKEN_ENDPOINT, refresh_token_endpoint=REFRESH_TOKEN_ENDPOINT, - revoke_token_endpoint=None, + revoke_token_endpoint=REVOKE_TOKEN_ENDPOINT, oauth_callback_route_name='feishu', default_scopes=DEFAULT_SCOPES, ) diff --git a/fastapi_oauth20/clients/gitee.py b/fastapi_oauth20/clients/gitee.py index 853891f..c6860a8 100644 --- a/fastapi_oauth20/clients/gitee.py +++ b/fastapi_oauth20/clients/gitee.py @@ -7,6 +7,7 @@ AUTHORIZE_ENDPOINT = 'https://gitee.com/oauth/authorize' ACCESS_TOKEN_ENDPOINT = 'https://gitee.com/oauth/token' REFRESH_TOKEN_ENDPOINT = ACCESS_TOKEN_ENDPOINT +REVOKE_TOKEN_ENDPOINT = None DEFAULT_SCOPES = ['user_info'] PROFILE_ENDPOINT = 'https://gitee.com/api/v5/user' @@ -19,7 +20,7 @@ def __init__(self, client_id: str, client_secret: str): authorize_endpoint=AUTHORIZE_ENDPOINT, access_token_endpoint=ACCESS_TOKEN_ENDPOINT, refresh_token_endpoint=REFRESH_TOKEN_ENDPOINT, - revoke_token_endpoint=None, + revoke_token_endpoint=REVOKE_TOKEN_ENDPOINT, oauth_callback_route_name='gitee', default_scopes=DEFAULT_SCOPES, ) diff --git a/fastapi_oauth20/clients/github.py b/fastapi_oauth20/clients/github.py index 3af4abe..a41380e 100644 --- a/fastapi_oauth20/clients/github.py +++ b/fastapi_oauth20/clients/github.py @@ -6,9 +6,10 @@ AUTHORIZE_ENDPOINT = 'https://github.com/login/oauth/authorize' ACCESS_TOKEN_ENDPOINT = 'https://github.com/login/oauth/access_token' +REFRESH_TOKEN_ENDPOINT = None +REVOKE_TOKEN_ENDPOINT = None DEFAULT_SCOPES = ['user', 'user:email'] PROFILE_ENDPOINT = 'https://api.github.com/user' -EMAILS_ENDPOINT = 'https://api.github.com/user/emails' class GitHubOAuth20(OAuth20Base): @@ -18,8 +19,8 @@ def __init__(self, client_id: str, client_secret: str): client_secret=client_secret, authorize_endpoint=AUTHORIZE_ENDPOINT, access_token_endpoint=ACCESS_TOKEN_ENDPOINT, - refresh_token_endpoint=None, - revoke_token_endpoint=None, + refresh_token_endpoint=REFRESH_TOKEN_ENDPOINT, + revoke_token_endpoint=REVOKE_TOKEN_ENDPOINT, oauth_callback_route_name='github', default_scopes=DEFAULT_SCOPES, ) @@ -35,7 +36,7 @@ async def get_userinfo(self, access_token: str) -> dict: email = res.get('email') if email is None: - response = await client.get(EMAILS_ENDPOINT) + response = await client.get(f'{PROFILE_ENDPOINT}/emails') await self.raise_httpx_oauth20_errors(response) emails = response.json() diff --git a/fastapi_oauth20/clients/google.py b/fastapi_oauth20/clients/google.py index d7c30f9..e361302 100644 --- a/fastapi_oauth20/clients/google.py +++ b/fastapi_oauth20/clients/google.py @@ -26,7 +26,7 @@ def __init__(self, client_id: str, client_secret: str): ) async def get_userinfo(self, access_token: str) -> dict: - """Gets user info from Google""" + """Get user info from Google""" headers = {'Authorization': f'Bearer {access_token}'} async with httpx.AsyncClient() as client: response = await client.get(PROFILE_ENDPOINT, headers=headers) diff --git a/fastapi_oauth20/clients/linuxdo.py b/fastapi_oauth20/clients/linuxdo.py new file mode 100644 index 0000000..73b0258 --- /dev/null +++ b/fastapi_oauth20/clients/linuxdo.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import httpx + +from fastapi_oauth20.oauth20 import OAuth20Base + +AUTHORIZE_ENDPOINT = 'https://connect.linux.do/oauth2/authorize' +ACCESS_TOKEN_ENDPOINT = 'https://connect.linux.do/oauth2/token' +REFRESH_TOKEN_ENDPOINT = ACCESS_TOKEN_ENDPOINT +REVOKE_TOKEN_ENDPOINT = None +DEFAULT_SCOPES = None +PROFILE_ENDPOINT = 'https://connect.linux.do/api/user' + + +class LinuxDoOAuth20(OAuth20Base): + def __init__(self, client_id: str, client_secret: str): + super().__init__( + client_id=client_id, + client_secret=client_secret, + authorize_endpoint=AUTHORIZE_ENDPOINT, + access_token_endpoint=ACCESS_TOKEN_ENDPOINT, + refresh_token_endpoint=REFRESH_TOKEN_ENDPOINT, + revoke_token_endpoint=REVOKE_TOKEN_ENDPOINT, + oauth_callback_route_name='linuxdo', + default_scopes=DEFAULT_SCOPES, + ) + + async def get_access_token(self, code: str, redirect_uri: str, code_verifier: str | None = None) -> dict: + """Obtain the token based on the Linux do authorization method""" + data = { + 'code': code, + 'redirect_uri': redirect_uri, + 'grant_type': 'authorization_code', + } + + auth = httpx.BasicAuth(self.client_id, self.client_secret) + + if code_verifier: + data.update({'code_verifier': code_verifier}) + async with httpx.AsyncClient(auth=auth) as client: + response = await client.post( + self.access_token_endpoint, + data=data, + headers=self.request_headers, + ) + await self.raise_httpx_oauth20_errors(response) + + res = response.json() + + return res + + async def get_userinfo(self, access_token: str) -> dict: + """Get user info from Linux Do""" + headers = {'Authorization': f'Bearer {access_token}'} + async with httpx.AsyncClient() as client: + response = await client.get(PROFILE_ENDPOINT, headers=headers) + await self.raise_httpx_oauth20_errors(response) + + res = response.json() + + return res diff --git a/fastapi_oauth20/clients/oschina.py b/fastapi_oauth20/clients/oschina.py index 9d0edd7..4211249 100644 --- a/fastapi_oauth20/clients/oschina.py +++ b/fastapi_oauth20/clients/oschina.py @@ -7,6 +7,8 @@ AUTHORIZE_ENDPOINT = 'https://www.oschina.net/action/oauth2/authorize' ACCESS_TOKEN_ENDPOINT = 'https://www.oschina.net/action/openapi/token' REFRESH_TOKEN_ENDPOINT = ACCESS_TOKEN_ENDPOINT +REVOKE_TOKEN_ENDPOINT = None +DEFAULT_SCOPES = None PROFILE_ENDPOINT = 'https://www.oschina.net/action/openapi/user' @@ -18,9 +20,9 @@ def __init__(self, client_id: str, client_secret: str): authorize_endpoint=AUTHORIZE_ENDPOINT, access_token_endpoint=ACCESS_TOKEN_ENDPOINT, refresh_token_endpoint=REFRESH_TOKEN_ENDPOINT, - revoke_token_endpoint=None, + revoke_token_endpoint=REVOKE_TOKEN_ENDPOINT, oauth_callback_route_name='oschina', - default_scopes=None, + default_scopes=DEFAULT_SCOPES, ) async def get_userinfo(self, access_token: str) -> dict: diff --git a/pyproject.toml b/pyproject.toml index 8739797..d83ad09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,9 +53,12 @@ order-by-type = true quote-style = "single" [tool.pdm] -package-type = "library" +distribution = true version = { source = "file", path = "fastapi_oauth20/__init__.py" } +[tool.pdm.scripts] +lint = "pre-commit run --all-files" + [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend"