Barazo default frontend barazo.forum
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(auth): sanitize login handle input and improve error message (#94)

Strip common input mistakes from the handle field before submitting:
leading @, bsky.app profile URLs, trailing dots, and mixed case.
Show a user-friendly message when handle resolution fails (502)
instead of the raw API error.

authored by

Guido X Jansen and committed by
GitHub
add0120d 3d9211c9

+69 -2
+55
src/__tests__/auth/login-page.test.tsx
··· 83 83 expect(mockLogin).toHaveBeenCalledWith('test.bsky.social') 84 84 }) 85 85 86 + it('strips leading @ from handle before login', async () => { 87 + const user = userEvent.setup() 88 + render(<LoginPage />) 89 + 90 + await user.type(screen.getByLabelText(/handle/i), '@test.bsky.social') 91 + await user.click(screen.getByRole('button', { name: /continue/i })) 92 + 93 + expect(mockLogin).toHaveBeenCalledWith('test.bsky.social') 94 + }) 95 + 96 + it('extracts handle from bsky.app profile URL', async () => { 97 + const user = userEvent.setup() 98 + render(<LoginPage />) 99 + 100 + await user.type(screen.getByLabelText(/handle/i), 'https://bsky.app/profile/test.bsky.social') 101 + await user.click(screen.getByRole('button', { name: /continue/i })) 102 + 103 + expect(mockLogin).toHaveBeenCalledWith('test.bsky.social') 104 + }) 105 + 106 + it('lowercases handle before login', async () => { 107 + const user = userEvent.setup() 108 + render(<LoginPage />) 109 + 110 + await user.type(screen.getByLabelText(/handle/i), 'Alice.Bsky.Social') 111 + await user.click(screen.getByRole('button', { name: /continue/i })) 112 + 113 + expect(mockLogin).toHaveBeenCalledWith('alice.bsky.social') 114 + }) 115 + 116 + it('strips trailing dot from handle', async () => { 117 + const user = userEvent.setup() 118 + render(<LoginPage />) 119 + 120 + await user.type(screen.getByLabelText(/handle/i), 'test.bsky.social.') 121 + await user.click(screen.getByRole('button', { name: /continue/i })) 122 + 123 + expect(mockLogin).toHaveBeenCalledWith('test.bsky.social') 124 + }) 125 + 126 + it('shows user-friendly error for unknown handle (502)', async () => { 127 + const { ApiError } = await import('@/lib/api/client') 128 + mockLogin.mockRejectedValueOnce(new ApiError(502, 'API 502: Bad Gateway')) 129 + 130 + const user = userEvent.setup() 131 + render(<LoginPage />) 132 + 133 + await user.type(screen.getByLabelText(/handle/i), 'nonexistent.bsky.social') 134 + await user.click(screen.getByRole('button', { name: /continue/i })) 135 + 136 + const alert = screen.getByRole('alert') 137 + expect(alert).toHaveTextContent(/couldn't find an account/i) 138 + expect(alert).toHaveTextContent('nonexistent.bsky.social') 139 + }) 140 + 86 141 it('has links to create accounts on PDS hosts', () => { 87 142 render(<LoginPage />) 88 143
+14 -2
src/app/login/page.tsx
··· 12 12 import Link from 'next/link' 13 13 import Image from 'next/image' 14 14 import { useAuth } from '@/hooks/use-auth' 15 + import { ApiError } from '@/lib/api/client' 15 16 import { cn } from '@/lib/utils' 16 17 17 18 function LoginContent() { ··· 36 37 37 38 const handleSubmit = async (e: React.FormEvent) => { 38 39 e.preventDefault() 39 - const trimmed = handle.trim() 40 + const trimmed = handle 41 + .trim() 42 + .replace(/^https?:\/\/bsky\.app\/profile\//i, '') 43 + .replace(/^@/, '') 44 + .replace(/\.$/, '') 45 + .toLowerCase() 40 46 if (!trimmed) { 41 47 setError('Please enter your handle') 42 48 return ··· 50 56 sessionStorage.setItem('auth_returnTo', returnTo) 51 57 await login(trimmed) 52 58 } catch (err) { 53 - setError(err instanceof Error ? err.message : 'Failed to start login') 59 + if (err instanceof ApiError && err.status === 502) { 60 + setError( 61 + `We couldn't find an account for "${trimmed}". Please check for typos and try again.` 62 + ) 63 + } else { 64 + setError(err instanceof Error ? err.message : 'Failed to start login') 65 + } 54 66 setSubmitting(false) 55 67 } 56 68 }