소스 검색

feat: add Feishu OAuth integration

Implement Feishu OAuth provider using standard client:
- Set up Feishu-specific endpoints for authorization, token, and userinfo
- Use user_id as sub claim for Feishu user identification
- Extract correct user information from nested 'data' field in Feishu responses

Configuration requirements:
- Set FEISHU_CLIENT_ID and FEISHU_CLIENT_SECRET environment variables to enable Feishu OAuth
- Set ENABLE_OAUTH_SIGNUP=true to allow automatic user creation after OAuth login
- Set DEFAULT_USER_ROLE=user to grant immediate access after OAuth registration
- Set OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true to enable merging of existing user accounts with matching emails
Xie Yanbo 2 달 전
부모
커밋
ee82439e67
4개의 변경된 파일65개의 추가작업 그리고 1개의 파일을 삭제
  1. 1 1
      backend/dev.sh
  2. 52 0
      backend/open_webui/config.py
  3. 2 0
      backend/open_webui/utils/oauth.py
  4. 10 0
      src/routes/auth/+page.svelte

+ 1 - 1
backend/dev.sh

@@ -1,3 +1,3 @@
-export CORS_ALLOW_ORIGIN="http://localhost:5173"
+export CORS_ALLOW_ORIGIN="http://localhost:5173;http://localhost:8080"
 PORT="${PORT:-8080}"
 uvicorn open_webui.main:app --port $PORT --host 0.0.0.0 --forwarded-allow-ips '*' --reload

+ 52 - 0
backend/open_webui/config.py

@@ -513,6 +513,30 @@ OAUTH_GROUPS_CLAIM = PersistentConfig(
     os.environ.get("OAUTH_GROUPS_CLAIM", os.environ.get("OAUTH_GROUP_CLAIM", "groups")),
 )
 
+FEISHU_CLIENT_ID = PersistentConfig(
+    "FEISHU_CLIENT_ID",
+    "oauth.feishu.client_id",
+    os.environ.get("FEISHU_CLIENT_ID", ""),
+)
+
+FEISHU_CLIENT_SECRET = PersistentConfig(
+    "FEISHU_CLIENT_SECRET",
+    "oauth.feishu.client_secret",
+    os.environ.get("FEISHU_CLIENT_SECRET", ""),
+)
+
+FEISHU_OAUTH_SCOPE = PersistentConfig(
+    "FEISHU_OAUTH_SCOPE",
+    "oauth.feishu.scope",
+    os.environ.get("FEISHU_OAUTH_SCOPE", "contact:user.base:readonly"),
+)
+
+FEISHU_REDIRECT_URI = PersistentConfig(
+    "FEISHU_REDIRECT_URI",
+    "oauth.feishu.redirect_uri",
+    os.environ.get("FEISHU_REDIRECT_URI", ""),
+)
+
 ENABLE_OAUTH_ROLE_MANAGEMENT = PersistentConfig(
     "ENABLE_OAUTH_ROLE_MANAGEMENT",
     "oauth.enable_role_mapping",
@@ -705,6 +729,32 @@ def load_oauth_providers():
             "register": oidc_oauth_register,
         }
 
+    if FEISHU_CLIENT_ID.value and FEISHU_CLIENT_SECRET.value:
+        def feishu_oauth_register(client: OAuth):
+            client.register(
+                name="feishu",
+                client_id=FEISHU_CLIENT_ID.value,
+                client_secret=FEISHU_CLIENT_SECRET.value,
+                access_token_url="https://open.feishu.cn/open-apis/authen/v2/oauth/token",
+                authorize_url="https://accounts.feishu.cn/open-apis/authen/v1/authorize",
+                api_base_url="https://open.feishu.cn/open-apis",
+                userinfo_endpoint="https://open.feishu.cn/open-apis/authen/v1/user_info",
+                client_kwargs={
+                    "scope": FEISHU_OAUTH_SCOPE.value,
+                    **(
+                        {"timeout": int(OAUTH_TIMEOUT.value)}
+                        if OAUTH_TIMEOUT.value
+                        else {}
+                    ),
+                },
+                redirect_uri=FEISHU_REDIRECT_URI.value,
+            )
+
+        OAUTH_PROVIDERS["feishu"] = {
+            "register": feishu_oauth_register,
+            "sub_claim": "user_id",
+        }
+
     configured_providers = []
     if GOOGLE_CLIENT_ID.value:
         configured_providers.append("Google")
@@ -712,6 +762,8 @@ def load_oauth_providers():
         configured_providers.append("Microsoft")
     if GITHUB_CLIENT_ID.value:
         configured_providers.append("GitHub")
+    if FEISHU_CLIENT_ID.value:
+        configured_providers.append("Feishu")
 
     if configured_providers and not OPENID_PROVIDER_URL.value:
         provider_list = ", ".join(configured_providers)

+ 2 - 0
backend/open_webui/utils/oauth.py

@@ -602,6 +602,8 @@ class OAuthManager:
                 or (auth_manager_config.OAUTH_USERNAME_CLAIM not in user_data)
             ):
                 user_data: UserInfo = await client.userinfo(token=token)
+            if provider == "feishu" and isinstance(user_data, dict) and "data" in user_data:
+                user_data = user_data["data"]
             if not user_data:
                 log.warning(f"OAuth callback failed, user data is missing: {token}")
                 raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)

+ 10 - 0
src/routes/auth/+page.svelte

@@ -523,6 +523,16 @@
 											>
 										</button>
 									{/if}
+									{#if $config?.oauth?.providers?.feishu}
+										<button
+											class="flex justify-center items-center bg-gray-700/5 hover:bg-gray-700/10 dark:bg-gray-100/5 dark:hover:bg-gray-100/10 dark:text-gray-300 dark:hover:text-white transition w-full rounded-full font-medium text-sm py-2.5"
+											on:click={() => {
+												window.location.href = `${WEBUI_BASE_URL}/oauth/feishu/login`;
+											}}
+										>
+											<span>{$i18n.t('Continue with {{provider}}', { provider: 'Feishu' })}</span>
+										</button>
+									{/if}
 								</div>
 							{/if}