From 8249e84454123c0d0d1a54216263b8cd7e3b379e Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:21:08 +0800 Subject: [PATCH] fix(auth): hide password routing implementation details Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- web/e2e/auth-entry.spec.ts | 8 ++++++++ web/src/i18n/locales/en.json | 1 - web/src/i18n/locales/ru.json | 1 - web/src/i18n/locales/zh.json | 1 - web/src/pages/login.test.tsx | 12 +++++++++++- web/src/pages/login.tsx | 14 +------------- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/web/e2e/auth-entry.spec.ts b/web/e2e/auth-entry.spec.ts index a7a6a3aa..2a639b53 100644 --- a/web/e2e/auth-entry.spec.ts +++ b/web/e2e/auth-entry.spec.ts @@ -8,9 +8,17 @@ test.describe('Auth Entry', () => { }) test('validates required fields and preserves returnTo on register link', async ({ page }) => { + await page.route('**/runtime-config.js', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/javascript', + body: 'window.__SKILLHUB_RUNTIME_CONFIG__ = { authDirectEnabled: "true", authDirectProvider: "local" }', + }) + }) await page.goto('/login?returnTo=%2Fdashboard%2Ftokens') await expect(page.getByRole('heading', { name: 'Login to SkillHub' })).toBeVisible() + await expect(page.getByText(/password compatibility layer/i)).toHaveCount(0) await page.getByRole('button', { name: 'Login' }).click() await expect(page.getByText('Username is required')).toBeVisible() diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 7b01d0bd..bcfcc801 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -360,7 +360,6 @@ "noAccount": "Don't have an account?", "register": "Sign up now", "oauthHint": "After OAuth authentication, you will be automatically redirected back to this site.", - "passwordCompatHint": "This deployment has the password compatibility layer enabled. The form will route to {{name}} instead of the fixed local account endpoint.", "enterpriseSsoTitle": "Enterprise SSO", "enterpriseSsoHint": "This deployment has the compatibility layer enabled. If your browser already has a {{name}} session, you can try establishing a SkillHub session directly.", "enterpriseSsoAutoHint": "This deployment has automatic {{name}} probing enabled. If it does not succeed, you can continue with the standard login methods.", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 2fa4f8d5..4fbb14b8 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -360,7 +360,6 @@ "noAccount": "Нет аккаунта?", "register": "Зарегистрироваться", "oauthHint": "После аутентификации OAuth вы будете автоматически перенаправлены обратно на этот сайт.", - "passwordCompatHint": "В этом развёртывании включён слой совместимости паролей. Форма направит запрос в {{name}} вместо фиксированной локальной учётной записи.", "enterpriseSsoTitle": "Корпоративный SSO", "enterpriseSsoHint": "В этом развёртывании включён слой совместимости. Если в браузере уже есть сессия {{name}}, можно попробовать сразу установить сессию SkillHub.", "enterpriseSsoAutoHint": "В этом развёртывании включено автоматическое зондирование {{name}}. Если оно не сработает, продолжайте стандартными способами входа.", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index c4fc51e0..36d43a7c 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -360,7 +360,6 @@ "noAccount": "还没有账号?", "register": "立即注册", "oauthHint": "使用 OAuth 登录时,认证完成后会自动返回当前站点。", - "passwordCompatHint": "当前部署已启用账号密码兼容接入层。表单将路由到 {{name}},而不是固定使用本地账号接口。", "enterpriseSsoTitle": "企业单点登录", "enterpriseSsoHint": "当前部署已启用兼容接入层。若浏览器中已存在 {{name}} 会话,可直接尝试建立 SkillHub 登录态。", "enterpriseSsoAutoHint": "当前部署已启用自动 {{name}} 探测。若未成功,你仍可继续使用现有登录方式。", diff --git a/web/src/pages/login.test.tsx b/web/src/pages/login.test.tsx index 3b7fca7d..2c35c14a 100644 --- a/web/src/pages/login.test.tsx +++ b/web/src/pages/login.test.tsx @@ -6,6 +6,7 @@ import { describe, expect, it, vi } from 'vitest' const authMethodsFixture = vi.hoisted(() => ({ methods: [] as Array<{ id: string, methodType: string }>, bootstrapEnabled: false, + directEnabled: false, isError: false, returnTo: '', navigate: vi.fn(), @@ -38,7 +39,7 @@ vi.mock('lucide-react', () => ({ })) vi.mock('@/api/client', () => ({ - getDirectAuthRuntimeConfig: () => ({ enabled: false }), + getDirectAuthRuntimeConfig: () => ({ enabled: authMethodsFixture.directEnabled, provider: 'local' }), getSessionBootstrapRuntimeConfig: () => ({ enabled: authMethodsFixture.bootstrapEnabled, provider: 'proxy' }), })) @@ -83,6 +84,7 @@ describe('LoginPage', () => { cleanup() authMethodsFixture.methods = [] authMethodsFixture.bootstrapEnabled = false + authMethodsFixture.directEnabled = false authMethodsFixture.isError = false authMethodsFixture.returnTo = '' authMethodsFixture.navigate.mockClear() @@ -103,6 +105,14 @@ describe('LoginPage', () => { expect(html).toContain('login.register') }) + it('does not expose password routing details when direct login is configured', () => { + authMethodsFixture.directEnabled = true + const html = renderToStaticMarkup() + + expect(html).toContain('login.submit') + expect(html).not.toContain('login.passwordCompatHint') + }) + it('returns to the home page after direct login without an explicit destination', async () => { render() fireEvent.change(screen.getByLabelText('login.username'), { target: { value: 'user1' } }) diff --git a/web/src/pages/login.tsx b/web/src/pages/login.tsx index 583c3fcf..31ece77d 100644 --- a/web/src/pages/login.tsx +++ b/web/src/pages/login.tsx @@ -2,7 +2,7 @@ import { Link, useNavigate, useSearch } from '@tanstack/react-router' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ArrowRight, Eye, EyeOff, LockKeyhole, UserRound } from 'lucide-react' -import { getDirectAuthRuntimeConfig, getSessionBootstrapRuntimeConfig } from '@/api/client' +import { getSessionBootstrapRuntimeConfig } from '@/api/client' import { AuthShell } from '@/features/auth/auth-shell' import { AuthMethodButtonList } from '@/features/auth/login-button' import { SessionBootstrapEntry } from '@/features/auth/session-bootstrap-entry' @@ -23,7 +23,6 @@ export function LoginPage() { const navigate = useNavigate() const search = useSearch({ from: '/login' }) const loginMutation = usePasswordLogin() - const directAuthConfig = getDirectAuthRuntimeConfig() const bootstrapConfig = getSessionBootstrapRuntimeConfig() const [username, setUsername] = useState('') const [password, setPassword] = useState('') @@ -34,10 +33,6 @@ export function LoginPage() { const returnTo = resolveAuthReturnTo(search.returnTo) const { data: authMethods, isLoading: authMethodsLoading } = useAuthMethods(returnTo) const disabledMessage = search.reason === 'accountDisabled' ? t('apiError.auth.accountDisabled') : null - const directMethod = directAuthConfig.provider - ? authMethods?.find((method) => - method.methodType === 'DIRECT_PASSWORD' && method.provider === directAuthConfig.provider) - : undefined const bootstrapMethod = authMethods?.find((method) => method.methodType === 'SESSION_BOOTSTRAP') const hasOrganizationMethod = bootstrapConfig.enabled const hasExternalMethods = authMethods?.some((method) => method.methodType === 'OAUTH_REDIRECT') @@ -97,13 +92,6 @@ export function LoginPage() {