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:
GitHub Actions
2025-12-21 23:46:25 +00:00
parent 15bb68106f
commit a5c86fc588
13 changed files with 812 additions and 360 deletions

View File

@@ -92,12 +92,19 @@ func TestSecurityHeaders(t *testing.T) {
},
},
{
name: "sets Cross-Origin-Opener-Policy",
name: "sets Cross-Origin-Opener-Policy in production",
isDevelopment: false,
checkHeaders: func(t *testing.T, resp *httptest.ResponseRecorder) {
assert.Equal(t, "same-origin", resp.Header().Get("Cross-Origin-Opener-Policy"))
},
},
{
name: "skips Cross-Origin-Opener-Policy in development",
isDevelopment: true,
checkHeaders: func(t *testing.T, resp *httptest.ResponseRecorder) {
assert.Empty(t, resp.Header().Get("Cross-Origin-Opener-Policy"))
},
},
{
name: "sets Cross-Origin-Resource-Policy",
isDevelopment: false,
@@ -155,6 +162,40 @@ func TestDefaultSecurityHeadersConfig(t *testing.T) {
assert.Nil(t, cfg.CustomCSPDirectives)
}
func TestSecurityHeaders_COOP_DevelopmentMode(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
cfg := SecurityHeadersConfig{IsDevelopment: true}
router.Use(SecurityHeaders(cfg))
router.GET("/test", func(c *gin.Context) {
c.Status(http.StatusOK)
})
req := httptest.NewRequest("GET", "/test", nil)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assert.Empty(t, resp.Header().Get("Cross-Origin-Opener-Policy"),
"COOP header should not be set in development mode")
}
func TestSecurityHeaders_COOP_ProductionMode(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
cfg := SecurityHeadersConfig{IsDevelopment: false}
router.Use(SecurityHeaders(cfg))
router.GET("/test", func(c *gin.Context) {
c.Status(http.StatusOK)
})
req := httptest.NewRequest("GET", "/test", nil)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assert.Equal(t, "same-origin", resp.Header().Get("Cross-Origin-Opener-Policy"),
"COOP header must be set in production mode")
}
func TestBuildCSP(t *testing.T) {
t.Run("production CSP", func(t *testing.T) {
csp := buildCSP(SecurityHeadersConfig{IsDevelopment: false})