### What problem does this PR solve? Deprecate `/github_callback` route in favor of `/oauth/callback/<channel>` for GitHub OAuth integration: - Added GitHub OAuth support in the authentication module - Introduced `GithubOAuthClient` with methods to fetch and normalize user info - Updated `CLIENT_TYPES` to include GitHub OAuth client - Deprecated `/github_callback` route and suggested using the generic `/oauth/callback/<channel>` route --- - Related pull requests: - #7379 - #7553 ### Usage - [Create a GitHub OAuth App](https://github.com/settings/applications/new) to obtain the `client_id` and `client_secret`, configure the authorization callback url: `https://your-app.com/v1/user/oauth/callback/github` - Edit `service_conf.yaml.template`: ```yaml # ... oauth: github: type: "github" icon: "github" display_name: "Github" client_id: "your_client_id" client_secret: "your_client_secret" redirect_uri: "https://your-app.com/v1/user/oauth/callback/github" # ... ``` ### Type of change - [x] Documentation Update - [x] Refactoring (non-breaking change)tags/v0.19.0
| @@ -32,8 +32,16 @@ oidc_config = { | |||
| "redirect_uri": "https://your-app.com/v1/user/oauth/callback/<channel>" | |||
| } | |||
| # Github OAuth configuration | |||
| github_config = { | |||
| "type": "github" | |||
| "client_id": "your_client_id", | |||
| "client_secret": "your_client_secret", | |||
| "redirect_uri": "https://your-app.com/v1/user/oauth/callback/<channel>" | |||
| } | |||
| # Get client instance | |||
| client = get_auth_client(oauth_config) # or oidc_config | |||
| client = get_auth_client(oauth_config) | |||
| ``` | |||
| ### Authentication Flow | |||
| @@ -16,11 +16,13 @@ | |||
| from .oauth import OAuthClient | |||
| from .oidc import OIDCClient | |||
| from .github import GithubOAuthClient | |||
| CLIENT_TYPES = { | |||
| "oauth2": OAuthClient, | |||
| "oidc": OIDCClient | |||
| "oidc": OIDCClient, | |||
| "github": GithubOAuthClient | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| # | |||
| # Copyright 2025 The InfiniFlow Authors. All Rights Reserved. | |||
| # | |||
| # Licensed under the Apache License, Version 2.0 (the "License"); | |||
| # you may not use this file except in compliance with the License. | |||
| # You may obtain a copy of the License at | |||
| # | |||
| # http://www.apache.org/licenses/LICENSE-2.0 | |||
| # | |||
| # Unless required by applicable law or agreed to in writing, software | |||
| # distributed under the License is distributed on an "AS IS" BASIS, | |||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| # See the License for the specific language governing permissions and | |||
| # limitations under the License. | |||
| # | |||
| import requests | |||
| from .oauth import OAuthClient, UserInfo | |||
| class GithubOAuthClient(OAuthClient): | |||
| def __init__(self, config): | |||
| """ | |||
| Initialize the GithubOAuthClient with the provider's configuration. | |||
| """ | |||
| config.update({ | |||
| "authorization_url": "https://github.com/login/oauth/authorize", | |||
| "token_url": "https://github.com/login/oauth/access_token", | |||
| "userinfo_url": "https://api.github.com/user", | |||
| "scope": "user:email" | |||
| }) | |||
| super().__init__(config) | |||
| def fetch_user_info(self, access_token, **kwargs): | |||
| """ | |||
| Fetch github user info. | |||
| """ | |||
| user_info = {} | |||
| try: | |||
| headers = {"Authorization": f"Bearer {access_token}"} | |||
| # user info | |||
| response = requests.get(self.userinfo_url, headers=headers, timeout=self.http_request_timeout) | |||
| response.raise_for_status() | |||
| user_info.update(response.json()) | |||
| # email info | |||
| response = requests.get(self.userinfo_url+"/emails", headers=headers, timeout=self.http_request_timeout) | |||
| response.raise_for_status() | |||
| email_info = response.json() | |||
| user_info["email"] = next( | |||
| (email for email in email_info if email["primary"]), None | |||
| )["email"] | |||
| return self.normalize_user_info(user_info) | |||
| except requests.exceptions.RequestException as e: | |||
| raise ValueError(f"Failed to fetch github user info: {e}") | |||
| def normalize_user_info(self, user_info): | |||
| email = user_info.get("email") | |||
| username = user_info.get("login", str(email).split("@")[0]) | |||
| nickname = user_info.get("name", username) | |||
| avatar_url = user_info.get("avatar_url", "") | |||
| return UserInfo(email=email, username=username, nickname=nickname, avatar_url=avatar_url) | |||
| @@ -233,6 +233,8 @@ def oauth_callback(channel): | |||
| @manager.route("/github_callback", methods=["GET"]) # noqa: F821 | |||
| def github_callback(): | |||
| """ | |||
| **Deprecated**, Use `/oauth/callback/<channel>` instead. | |||
| GitHub OAuth callback endpoint. | |||
| --- | |||
| tags: | |||
| @@ -64,9 +64,12 @@ redis: | |||
| # base_url: '' | |||
| # oauth: | |||
| # github: | |||
| # type: github | |||
| # icon: github | |||
| # display_name: "Github" | |||
| # client_id: xxxxxxxxxxxxxxxxxxxxxxxxx | |||
| # secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxx | |||
| # url: https://github.com/login/oauth/access_token | |||
| # client_secret: xxxxxxxxxxxxxxxxxxxxxxxx | |||
| # redirect_uri: https://your-app.com/v1/user/oauth/callback/github | |||
| # feishu: | |||
| # app_id: cli_xxxxxxxxxxxxxxxxxxx | |||
| # app_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxx | |||
| @@ -76,9 +76,12 @@ redis: | |||
| # image2text_model: '' | |||
| # oauth: | |||
| # github: | |||
| # type: github | |||
| # icon: github | |||
| # display_name: "Github" | |||
| # client_id: xxxxxxxxxxxxxxxxxxxxxxxxx | |||
| # secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxx | |||
| # url: https://github.com/login/oauth/access_token | |||
| # client_secret: xxxxxxxxxxxxxxxxxxxxxxxx | |||
| # redirect_uri: https://your-app.com/v1/user/oauth/callback/github | |||
| # feishu: | |||
| # app_id: cli_xxxxxxxxxxxxxxxxxxx | |||
| # app_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxx | |||