Repository navigation
fix: chat user login #7791
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: chat user login #7791
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,7 +50,6 @@ class PortalSerializer(serializers.Serializer): | |
| enable_knowledge_base_api = serializers.BooleanField( | ||
| required=False, label=_("enable knowledge base api"), help_text=_("enable knowledge base api") | ||
| ) | ||
| enable_auth = serializers.BooleanField(required=False, label=_("enable auth"), help_text=_("enable auth")) | ||
| auth_config = serializers.JSONField(required=False, label=_("auth config"), help_text=_("auth config")) | ||
| enable_cors = serializers.BooleanField(required=False, label=_("enable cors"), help_text=_("enable cors")) | ||
| cross_domain_list = serializers.ListSerializer( | ||
|
|
@@ -62,7 +61,7 @@ class PortalSerializer(serializers.Serializer): | |
| class Model(serializers.ModelSerializer): | ||
| class Meta: | ||
| model = Portal | ||
| fields = "__all__" | ||
| exclude = ("enable_auth",) | ||
|
|
||
| @staticmethod | ||
| def _is_license_valid(): | ||
|
|
@@ -111,11 +110,12 @@ def one(self): | |
| if portal is None: | ||
| raise AppApiException(500, _("Portal configuration does not exist")) | ||
| data = PortalSerializer.Model(portal).data | ||
| # 授权无效时不展示 auth_config,避免暴露认证配置 | ||
| # 授权无效时不展示 auth_config 与 logo,避免暴露认证配置与品牌信息 | ||
| if self._is_license_valid(): | ||
| data["auth_config"] = self._enrich_auth_config(data["auth_config"]) | ||
| else: | ||
| data["auth_config"] = {} | ||
| data["logo"] = "" | ||
| return data | ||
|
|
||
| def _upload_file(self, file_obj): | ||
|
|
@@ -152,8 +152,9 @@ def edit(self, instance, with_valid=True): | |
| if field == "auth_config" and isinstance(value, dict): | ||
| value = {**(portal.auth_config or {}), **value} | ||
| setattr(portal, field, value) | ||
| # Logo 为授权功能,授权无效时忽略 Logo 上传/修改 | ||
| for field_name in ["logo"]: | ||
| if field_name in instance: | ||
| if field_name in instance and self._is_license_valid(): | ||
| self._handle_file_field(portal, field_name, instance.get(field_name)) | ||
| portal.save() | ||
| return PortalSerializer.Model(portal).data | ||
|
|
@@ -166,13 +167,13 @@ def get_login_profile(): | |
| if portal is None: | ||
| raise AppApiException(500, _("Portal configuration does not exist")) | ||
| auth_config = portal.auth_config or {} | ||
| license_valid = PortalSerializer._is_license_valid() | ||
| return { | ||
| "name": portal.name, | ||
| "description": portal.description or "", | ||
| "logo": portal.logo or "", | ||
| "enable_auth": portal.enable_auth, | ||
| "authentication_type": auth_config.get("type", "LOCAL") if portal.enable_auth else "", | ||
| "login_value": auth_config.get("login_value", []) if portal.enable_auth else [], | ||
| "max_attempts": auth_config.get("max_attempts", 1) if portal.enable_auth else 1, | ||
| "logo": (portal.logo or "") if license_valid else "", | ||
| "authentication_type": auth_config.get("type", "LOCAL"), | ||
| "login_value": auth_config.get("login_value", []), | ||
| "max_attempts": auth_config.get("max_attempts", 1), | ||
| "rsa_key": get_key_pair_by_sql().get("key", ""), | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code diff refactors the handling of 1. Critical Logic Issue: Removal of
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,6 @@ | |
| from portal.api.portal import PortalAPI | ||
| from portal.serializers.portal import ( | ||
| PortalSerializer, | ||
| PortalLoginSerializer, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -55,15 +54,3 @@ def get(self, request: Request): | |
| @has_permissions(PermissionConstants.PORTAL_EDIT, RoleConstants.ADMIN) | ||
| def put(self, request: Request): | ||
| return result.success(PortalSerializer(data=request.data).edit(request.data)) | ||
|
|
||
|
|
||
| class PortalInfoView(APIView): | ||
| @extend_schema( | ||
| methods=["GET"], | ||
| description=_("Get portal login info"), | ||
| summary=_("Get portal login info"), | ||
| operation_id=_("Get portal login info"), # type: ignore | ||
| tags=[_("Portal")], # type: ignore | ||
| ) | ||
| def get(self, request: Request): | ||
| return result.success(PortalLoginSerializer.get_login_profile()) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is a review of the provided code diff: 1. Irregularities & Potential Issues
2. Optimization Suggestions
3. Summary✅ Clean Deletion Patch: The diff is syntactically correct and logically consistent. No code-level bugs detected in the snippet itself. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
| import { computed, onBeforeMount, ref } from 'vue' | ||
| import { ArrowLeft } from '@element-plus/icons-vue' | ||
| import { getDingTalkClientAuthCode } from '@/views/login/dingtalk-client-login' | ||
| import { useRoute, useRouter, type RouteLocationRaw } from 'vue-router' | ||
| import { type RouteLocationRaw, useRoute, useRouter } from 'vue-router' | ||
| import { v7 as uuidv7 } from 'uuid' | ||
| import ChatAuthApi from '@/api/chat/auth' | ||
| import { CHAT_API_BASE_PATH } from '@/api/constants' | ||
|
|
@@ -25,11 +25,15 @@ const loginTitle = computed(() => { | |
| return (auth.authProfile?.meta as ApplicationAuthProfileMeta | undefined)?.application_name || '登录' | ||
| }) | ||
|
|
||
| // 认证前置配置只有可用登录方式,默认登录方式依次取本地、LDAP、第一个可用方式 | ||
| // 认证前置配置只有可用登录方式,默认登录方式优先取后端配置,再依次回退本地、LDAP、第一个可用方式 | ||
| const loginConfig = computed<LoginConfig>(() => { | ||
| const loginMethods = auth.authProfile?.login_value?.length ? auth.authProfile.login_value : [LOGIN_METHOD.LOCAL] | ||
| const configuredDefault = auth.authProfile?.default_value | ||
| const defaultLoginMethod = | ||
| [LOGIN_METHOD.LOCAL, LOGIN_METHOD.LDAP].find((method) => loginMethods.includes(method)) ?? loginMethods[0] ?? LOGIN_METHOD.LOCAL | ||
| (configuredDefault && loginMethods.includes(configuredDefault) ? configuredDefault : undefined) ?? | ||
| [LOGIN_METHOD.LOCAL, LOGIN_METHOD.LDAP].find((method) => loginMethods.includes(method)) ?? | ||
| loginMethods[0] ?? | ||
| LOGIN_METHOD.LOCAL | ||
| return { default_value: defaultLoginMethod, login_methods: loginMethods, max_attempts: auth.authProfile?.max_attempts ?? 1 } | ||
| }) | ||
|
|
||
|
|
@@ -75,7 +79,7 @@ const getExternalLoginUrl = (authType: LoginMethod): Promise<string> => { | |
| if (!config.authEndpoint || !config.clientId) return '' | ||
| const redirectUrl = `${config.redirectUrl}${queryString ? `?${queryString}` : ''}` | ||
| const state = accessToken ? `${uuidv7()}_${accessToken}` : uuidv7() | ||
| let url = `${config.authEndpoint}?client_id=${config.clientId}&response_type=code&redirect_uri=${redirectUrl}&state=${state}` | ||
| let url = `${config.authEndpoint}?client_id=${config.clientId}&response_type=code&redirect_uri=${config.redirectUrl}&state=${state}` | ||
| if (config.scope) url += `&scope=${config.scope}` | ||
| return url | ||
| }) | ||
|
|
@@ -102,11 +106,10 @@ const loginService: LoginService = { | |
| }, | ||
| login: (loginRequest) => auth.login(loginRequest), | ||
| ldapLogin: (loginForm) => auth.ldapLogin(loginForm), | ||
| // 验证码:后端按失败次数决定是否需要,无需时返回空字符串;目前仅应用场景支持 | ||
| // 验证码:后端按失败次数决定是否需要,无需时返回空字符串 | ||
| getCaptcha: (username) => { | ||
| const { accessToken } = auth | ||
| if (!accessToken || !username.trim()) return Promise.resolve('') | ||
| return ChatAuthApi.getCaptcha(username, accessToken).then(({ captcha }) => captcha) | ||
| if (!username.trim()) return Promise.resolve('') | ||
| return ChatAuthApi.getCaptcha(username, auth.accessToken).then(({ captcha }) => captcha) | ||
| }, | ||
| getExternalLoginUrl, | ||
| getQrCodeSources: () => ChatAuthApi.getQrCodeSources(), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is a code review focusing on the changes in the provided diff. 1. Critical Security Bug: Missing URL EncodingLocation: - let url = `${config.authEndpoint}?client_id=${config.clientId}&response_type=code&redirect_uri=${redirectUrl}&state=${state}`
+ let url = `${config.authEndpoint}?client_id=${config.clientId}&response_type=code&redirect_uri=${config.redirectUrl}&state=${state}`Issue:
Recommendation:
const redirectUrl = config.redirectUrl + (queryString ? `?${queryString}` : '');
let url = `${config.authEndpoint}?client_id=${encodeURIComponent(config.clientId)}&response_type=code&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${encodeURIComponent(state)}`;
if (config.scope) url += `&scope=${encodeURIComponent(config.scope)}`;2. Logic Regression in
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,6 +105,8 @@ onMounted(() => loadPortalSetting()) | |
|
|
||
| // 权限 控制 | ||
| const canEditPortal = computed(() => perm.system.portal.edit()) | ||
| // Logo 为授权功能,授权无效(含社区版)时仅可查看,禁止上传或更换 Logo | ||
| const canEditLogo = computed(() => perm.system.portal.edit() && (auth.isPE || auth.isEE)) | ||
| </script> | ||
|
|
||
| <template> | ||
|
|
@@ -118,7 +120,13 @@ const canEditPortal = computed(() => perm.system.portal.edit()) | |
| <h4 class="truncate" :title="previewName">{{ previewName }}</h4> | ||
| </div> | ||
| <!-- 编辑门户名称与 Logo --> | ||
| <ButtonEditPortal :setting="portalSetting" :saving="saving" :disabled="!canEditPortal" :save="savePortalSetting" /> | ||
| <ButtonEditPortal | ||
| :setting="portalSetting" | ||
| :saving="saving" | ||
| :disabled="!canEditPortal" | ||
| :logo-disabled="!canEditLogo" | ||
| :save="savePortalSetting" | ||
| /> | ||
| </div> | ||
| </el-card> | ||
| <el-card shadow="never"> | ||
|
|
@@ -197,12 +205,11 @@ const canEditPortal = computed(() => perm.system.portal.edit()) | |
| /> | ||
| </div> | ||
|
|
||
| <div class="flex-between gap-2"> | ||
| <div class="flex-between gap-2" v-if="auth.isPE || auth.isEE"> | ||
| <span>登录设置</span> | ||
| <div class="flex-align-center gap-2"> | ||
| <!-- 配置登录设置 --> | ||
| <ButtonPortalAuthSetting | ||
| v-if="auth.isPE || auth.isEE" | ||
| ref="authSettingButtonRef" | ||
| :setting="portalSetting" | ||
| :saving="saving" | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is a review of the provided code snippet, focusing on logic, consistency, and potential issues. 1. Logic & Consistency IssuesA. Inconsistent Permission Logic for "Login Settings"
B. Redundant
2. Code Quality & OptimizationA. Prop Naming Clarity
B. Template Structure
3. Potential Bugs
Summary of Recommendations
Improved Code Suggestion (for consistency)// In <script>
const isEnterprise = computed(() => auth.isPE || auth.isEE)
const canEditPortal = computed(() => perm.system.portal.edit())
const canEditLogo = computed(() => canEditPortal.value && isEnterprise.value)<!-- In <template> -->
<!-- Option A: Hide Login Settings for Non-Enterprise (Current Behavior) -->
<div class="flex-between gap-2" v-if="isEnterprise"> ... </div>
<!-- OR Option B: Show but Disable (Consistent with Logo) -->
<div class="flex-between gap-2">
<span>登录设置</span>
<ButtonPortalAuthSetting
:disabled="!isEnterprise || !canEditPortal"
...
/>
</div> |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a review of the code changes, highlighting potential issues, logic flaws, and optimization suggestions.
1. Critical Logic Issue: Inconsistency between
get_login_profileand Internal StateIn
get_login_profile, you removed the check forportal.enable_authwhen derivingauthentication_type,login_value, andmax_attempts. You also removedenable_authfrom the response entirely.enable_authto decide whether to show authentication controls. By removing it but still returningauthentication_type(e.g., "LOCAL") even when auth might be effectively disabled or unconfigured, you create ambiguity.auth_configis empty or invalid,auth_config.get("type", "LOCAL")returns"LOCAL". The frontend will seeauthentication_type: "LOCAL"and might assume authentication is enabled and configured, leading to login failures if the backend doesn't enforce it correctly elsewhere.enable_authin the response (but derive it carefully) or ensureauthentication_typereturnsNone/"DISABLED"explicitly if auth is not active/authorized.2. Security/Logic Flaw: License Check Coupling
You coupled
logo handlingandauth_config exposurestrictly to_is_license_valid(). However, you removedenable_authfrom the serializer/model entirely (exclude = ("enable_auth,",)).enable_auth=Falsebut an invalid license, the previous code would hideauth_config. The new code hidesauth_configandlogobased on license validity, regardless of the actualenable_authstate (since the field is gone from the model view).enable_authstill a column in the database?fieldsviaexcludehides it from API responses, but it remains in the DB. This is fine if intentional.enable_auth=False) with a valid license? The code still returnsauthentication_type: "LOCAL"because it reads directly fromauth_config. The logicif portal.enable_auth else ""was removed.authentication_typeinget_login_profileto reflect whether auth is actually enabled, OR ensure that settingenable_auth=Falseclears/neutralizesauth_configin_enrich_auth_configoreditmethod.3. Redundant/Unsafe File Handling
self._is_license_valid()twice (once here, once potentially inone()or other paths) is inefficient if it involves DB queries or complex checks.4. Serializer Model Configuration
enable_authis indeed a legacy field that can be safely excluded. If this field is used in business logic elsewhere (e.g., middleware checkingPortal.objects.first().enable_auth), removing it from the serializer won't break internal calls, but it breaks API consumers. Confirm API clients don't depend onenable_auth.5.
get_login_profileOptimization & Clarityone()method, you cleardata["logo"] = ""if license is invalid. Inget_login_profile, you do the same. This is consistent.get_login_profile, you accessportal.auth_configdirectly from the DB object (portal.auth_config). Ensure this matches the enriched version returned byone(). Currently,one()enrichesauth_configonly if license is valid, otherwise sets it to{}.get_login_profilereads raw DB data. This could lead to inconsistency between/oneand/login_profileendpoints regarding whatauth_configcontent looks like.6. General Suggestions
a. Extract License Check to Context/Decorator
Since
_is_license_valid()is used inone,edit, andget_login_profile, consider using a decorator or middleware to injectlicense_validinto the view/request context to avoid repeated calls and improve performance.b. Explicit Error Handling for Disabled Features
Instead of silently ignoring logo uploads when license is invalid, consider raising a specific exception:
c. Clean Up Removed Imports
Ensure that if
enable_authwas imported from models or utilities specifically for this serializer/view, those unused imports are removed.Summary of Recommended Fixes
Restore Logic for Auth Type in
get_login_profile:Without this, you're returning config details even if the system shouldn't use them.
Cache License Validity:
Pass
license_validas a parameter or store it in the service class attribute during initialization if per-request.User Feedback:
Add errors when users try to modify licensed features (
logo,auth_config) without a valid license, instead of silent ignores.Verify Frontend Contract:
Confirm the frontend no longer expects
enable_authormax_attemptswhenauthentication_typeis empty/disabled.