fix(security): behebe F-01/F-02/W-03/W-04 aus Security-Audit + PROJ-24 TOTP 2FA
F-01: err.Error() wird nicht mehr an HTTP-Clients gesendet.
Stattdessen generische Fehlermeldungen + Server-Log.
Betrifft: handleCreateUser, handleUpdateUser, handleDeleteUser,
handleSyncNow, handleSecurityConfig, handleUpload.
F-02: Login-Audit-Log enthält keinen rohen err.Error() mehr.
Neue classifyLoginError() Funktion: invalid_password / ldap_error /
account_disabled / unknown — schützt vor LDAP-Info-Leak via Audit-API.
W-03: remoteIP() trimmt jetzt Leerzeichen aus X-Forwarded-For.
Vollständige Lösung erfordert Proxy-Konfiguration (W-03 bleibt WARN).
W-04: Attachment-Dateiname wird durch sanitizeFilename() bereinigt.
Nur [a-zA-Z0-9._- ] erlaubt — verhindert Header-Injection.
PROJ-24: TOTP 2FA vollständig implementiert:
- internal/auth/totp.go: GenerateSecret, ValidateTOTP, QRCodeSVG
- internal/api/totp_handlers.go: Setup, Login-Step2, Admin-Reset
- internal/userstore: SetTOTPSecret, EnableTOTP, DisableTOTP, ResetTOTP
- Login-Flow: totp_pending JWT → /api/auth/totp → vollwertiger JWT
- AES-256-GCM verschlüsseltes Secret in users.totp_secret
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,17 +30,20 @@ func newTestAuth(t *testing.T) (*auth.Manager, *userstore.Store) {
|
||||
Role: userstore.RoleUser,
|
||||
})
|
||||
|
||||
mgr := auth.New(store, nil, "test-jwt-secret-32chars-long-enough")
|
||||
mgr := auth.New(store, nil, "test-jwt-secret-32chars-long-enough", "0000000000000000000000000000000000000000000000000000000000000000")
|
||||
return mgr, store
|
||||
}
|
||||
|
||||
func TestLoginSuccess(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
token, user, err := mgr.Login("testadmin", "adminpass")
|
||||
token, user, totpRequired, err := mgr.Login("testadmin", "adminpass")
|
||||
if err != nil {
|
||||
t.Fatalf("Login: %v", err)
|
||||
}
|
||||
if totpRequired {
|
||||
t.Error("expected totpRequired=false for user without TOTP")
|
||||
}
|
||||
if token == "" {
|
||||
t.Error("expected non-empty token")
|
||||
}
|
||||
@@ -55,7 +58,7 @@ func TestLoginSuccess(t *testing.T) {
|
||||
func TestLoginWrongPassword(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
if _, _, err := mgr.Login("testadmin", "wrongpass"); err == nil {
|
||||
if _, _, _, err := mgr.Login("testadmin", "wrongpass"); err == nil {
|
||||
t.Error("expected error for wrong password")
|
||||
}
|
||||
}
|
||||
@@ -63,7 +66,7 @@ func TestLoginWrongPassword(t *testing.T) {
|
||||
func TestLoginUnknownUser(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
if _, _, err := mgr.Login("nobody", "pw"); err == nil {
|
||||
if _, _, _, err := mgr.Login("nobody", "pw"); err == nil {
|
||||
t.Error("expected error for unknown user")
|
||||
}
|
||||
}
|
||||
@@ -71,7 +74,7 @@ func TestLoginUnknownUser(t *testing.T) {
|
||||
func TestTokenValidation(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
token, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
token, _, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
sess, err := mgr.ValidateToken(token)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateToken: %v", err)
|
||||
@@ -90,7 +93,7 @@ func TestTokenValidation(t *testing.T) {
|
||||
func TestTokenTampering(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
token, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
token, _, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
tampered := token + "x"
|
||||
|
||||
if _, err := mgr.ValidateToken(tampered); err == nil {
|
||||
@@ -101,7 +104,7 @@ func TestTokenTampering(t *testing.T) {
|
||||
func TestLogout(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
token, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
token, _, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
|
||||
// Token valid before logout
|
||||
if _, err := mgr.ValidateToken(token); err != nil {
|
||||
@@ -146,8 +149,8 @@ func TestHasRole(t *testing.T) {
|
||||
func TestMultipleSessionsIndependent(t *testing.T) {
|
||||
mgr, _ := newTestAuth(t)
|
||||
|
||||
token1, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
token2, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
token1, _, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
token2, _, _, _ := mgr.Login("testadmin", "adminpass")
|
||||
|
||||
if token1 == token2 {
|
||||
t.Error("two logins should produce different tokens (different JTIs)")
|
||||
|
||||
Reference in New Issue
Block a user