diff --git a/web/e2e/auth-entry.spec.ts b/web/e2e/auth-entry.spec.ts index 2a639b53..ef4f6c3e 100644 --- a/web/e2e/auth-entry.spec.ts +++ b/web/e2e/auth-entry.spec.ts @@ -86,6 +86,18 @@ test.describe('Auth Entry', () => { await expect(page.getByText('Sign in directly with your existing OAuth account')).toHaveCount(0) }) + test('keeps the login form available when the session status check fails', async ({ page }) => { + await page.route('**/api/v1/auth/me', async (route) => { + await route.fulfill({ status: 503, contentType: 'application/json', body: '{"code":503,"msg":"Unavailable"}' }) + }) + + await page.goto('/login') + + await expect(page.getByLabel('Username')).toBeVisible() + await expect(page.getByLabel('Password', { exact: true })).toBeVisible() + await expect(page.getByRole('button', { name: 'Login' })).toBeVisible() + }) + test('keeps configured session bootstrap available in the organization view', async ({ page }) => { await page.route('**/runtime-config.js', async (route) => { await route.fulfill({ diff --git a/web/src/features/auth/auth-shell.test.tsx b/web/src/features/auth/auth-shell.test.tsx index 0e4ea4d7..f8d92c6d 100644 --- a/web/src/features/auth/auth-shell.test.tsx +++ b/web/src/features/auth/auth-shell.test.tsx @@ -9,6 +9,7 @@ describe('AuthShell', () => { expect(html).toContain('dark:bg-slate-950') expect(html).toContain('sticky top-0 hidden h-svh') expect(html).toContain('authShell.heroTitle') + expect(html).toContain('authShell.shortTagline') expect(html).toContain('login-skill-art-light.png') expect(html).toContain('login-skill-art-dark.png') expect(html).toContain('authShell.backHome') diff --git a/web/src/features/auth/auth-shell.tsx b/web/src/features/auth/auth-shell.tsx index 308d2d71..1c1aea75 100644 --- a/web/src/features/auth/auth-shell.tsx +++ b/web/src/features/auth/auth-shell.tsx @@ -48,7 +48,7 @@ export function AuthShell({ children, className }: AuthShellProps) { -

Skills
Build
Together

+

{t('authShell.shortTagline')}

diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index bcfcc801..4c25114a 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1503,6 +1503,7 @@ "heroTitle": "Discover, preserve, and reuse\nyour team's skills", "heroSubtitle": "Start with a SKILL.md.\nTurn experience into a capability your team can share.", "heroFooter": "Bring team knowledge together and unlock skill value", + "shortTagline": "Skills\nBuild\nTogether", "capabilities": "What you can do with SkillHub", "discoverTitle": "Discover skills", "discoverDescription": "Find reusable skills and practices across your team.", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 4fbb14b8..fc0688d7 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -1614,6 +1614,7 @@ "heroTitle": "Открывайте и сохраняйте\nнавыки вашей команды", "heroSubtitle": "Начните с SKILL.md.\nПревратите опыт в доступный команде навык.", "heroFooter": "Собирайте знания команды и раскрывайте ценность навыков", + "shortTagline": "Навыки\nобъединяют\nкоманду", "capabilities": "Возможности SkillHub", "discoverTitle": "Поиск навыков", "discoverDescription": "Находите навыки и практики, которые команда может использовать повторно.", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 36d43a7c..f900f983 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -1503,6 +1503,7 @@ "heroTitle": "让团队的技能\n被发现、被沉淀、被复用", "heroSubtitle": "从一份 SKILL.md 开始,\n把经验转化为可共享的团队能力。", "heroFooter": "汇聚团队智慧,释放技能价值", + "shortTagline": "让技能\n连接团队", "capabilities": "SkillHub 的能力", "discoverTitle": "发现技能", "discoverDescription": "找到团队可复用的技能包与实践。", diff --git a/web/src/shared/lib/auth-route.test.ts b/web/src/shared/lib/auth-route.test.ts index b164dd06..eb5888d1 100644 --- a/web/src/shared/lib/auth-route.test.ts +++ b/web/src/shared/lib/auth-route.test.ts @@ -62,6 +62,14 @@ describe('auth-route', () => { await expect(redirectAuthenticated({ search: { returnTo: '/dashboard/tokens' } })).resolves.toBeUndefined() }) + it('keeps the login page available when the session status check fails', async () => { + const redirectAuthenticated = createRedirectAuthenticated(async () => { + throw new Error('Session status unavailable') + }) + + await expect(redirectAuthenticated({ search: { returnTo: '/dashboard/tokens' } })).resolves.toBeUndefined() + }) + it('redirects authenticated visitors to the requested local page', async () => { const redirectAuthenticated = createRedirectAuthenticated(async () => ({ userId: 'user-1' })) diff --git a/web/src/shared/lib/auth-route.ts b/web/src/shared/lib/auth-route.ts index 00a21e4a..ada9d746 100644 --- a/web/src/shared/lib/auth-route.ts +++ b/web/src/shared/lib/auth-route.ts @@ -40,7 +40,14 @@ function resolveAuthenticatedReturnTo(value: unknown) { export function createRedirectAuthenticated(getCurrentUser: () => Promise) { return async function redirectAuthenticated({ search }: { search: { returnTo?: string } }) { - if (await getCurrentUser()) { + let user: unknown + try { + user = await getCurrentUser() + } catch { + // A failed status check must not make the login entry unavailable. + return + } + if (user) { throw redirect({ to: resolveAuthenticatedReturnTo(search.returnTo), replace: true }) } }