fix: login page browser warnings and password manager support
- Make COOP header conditional on development mode to suppress HTTP warnings - Add autocomplete attributes to all email/password inputs for password manager compatibility - Add comprehensive tests for COOP conditional behavior - Update security documentation for COOP, HTTPS requirements, and autocomplete Fixes browser console warnings and improves UX by enabling password managers. All quality gates passed: 85.7% backend coverage, 86.46% frontend coverage, zero security issues, all pre-commit hooks passed. Changes: - Backend: backend/internal/api/middleware/security.go - Frontend: Login, Setup, Account, AcceptInvite, SMTPSettings pages - Tests: Added 4 new test cases (2 backend, 2 frontend) - Docs: Updated security.md, getting-started.md, README.md
This commit is contained in:
@@ -171,6 +171,7 @@ export default function AcceptInvite() {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<PasswordStrengthMeter password={password} />
|
||||
</div>
|
||||
@@ -187,6 +188,7 @@ export default function AcceptInvite() {
|
||||
? t('acceptInvite.passwordsDoNotMatch')
|
||||
: undefined
|
||||
}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -380,6 +380,7 @@ export default function Account() {
|
||||
value={oldPassword}
|
||||
onChange={(e) => setOldPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -390,6 +391,7 @@ export default function Account() {
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<PasswordStrengthMeter password={newPassword} />
|
||||
</div>
|
||||
@@ -402,6 +404,7 @@ export default function Account() {
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
error={confirmPassword && newPassword !== confirmPassword ? t('account.passwordsDoNotMatch') : undefined}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -86,6 +86,7 @@ export default function Login() {
|
||||
required
|
||||
placeholder="admin@example.com"
|
||||
disabled={loading}
|
||||
autoComplete="email"
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<Input
|
||||
@@ -96,6 +97,7 @@ export default function Login() {
|
||||
required
|
||||
placeholder="••••••••"
|
||||
disabled={loading}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
|
||||
@@ -174,6 +174,7 @@ export default function SMTPSettings() {
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -185,6 +186,7 @@ export default function SMTPSettings() {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
helperText={t('smtp.passwordHelper')}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,6 +127,7 @@ const Setup: FC = () => {
|
||||
value={formData.email}
|
||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||
className={emailValid === false ? 'border-red-500 focus:ring-red-500' : emailValid === true ? 'border-green-500 focus:ring-green-500' : ''}
|
||||
autoComplete="email"
|
||||
/>
|
||||
{emailValid === false && (
|
||||
<p className="mt-1 text-xs text-red-500">{t('setup.invalidEmail')}</p>
|
||||
@@ -142,6 +143,7 @@ const Setup: FC = () => {
|
||||
placeholder="••••••••"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<PasswordStrengthMeter password={formData.password} />
|
||||
</div>
|
||||
|
||||
@@ -77,4 +77,17 @@ describe('<Login />', () => {
|
||||
await waitFor(() => expect(postSpy).toHaveBeenCalled())
|
||||
expect(loginFn).toHaveBeenCalledWith('bearer-token')
|
||||
})
|
||||
|
||||
it('has proper autocomplete attributes for password managers', async () => {
|
||||
vi.spyOn(setupApi, 'getSetupStatus').mockResolvedValue({ setupRequired: false })
|
||||
renderWithProviders(<Login />)
|
||||
|
||||
await waitFor(() => screen.getByPlaceholderText(/admin@example.com/i))
|
||||
|
||||
const emailInput = screen.getByPlaceholderText(/admin@example.com/i)
|
||||
const passwordInput = screen.getByPlaceholderText(/••••••••/i)
|
||||
|
||||
expect(emailInput).toHaveAttribute('autocomplete', 'email')
|
||||
expect(passwordInput).toHaveAttribute('autocomplete', 'current-password')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -147,4 +147,20 @@ describe('Setup Page', () => {
|
||||
expect(screen.getByText('Setup failed')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('has proper autocomplete attributes for password managers', async () => {
|
||||
vi.mocked(setupApi.getSetupStatus).mockResolvedValue({ setupRequired: true });
|
||||
|
||||
renderWithProviders(<Setup />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Welcome to Charon')).toBeTruthy();
|
||||
});
|
||||
|
||||
const emailInput = screen.getByLabelText('Email Address')
|
||||
const passwordInput = screen.getByLabelText('Password')
|
||||
|
||||
expect(emailInput).toHaveAttribute('autocomplete', 'email')
|
||||
expect(passwordInput).toHaveAttribute('autocomplete', 'new-password')
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user