TASK-031: Enforce bcrypt 72-byte password limit

Bcrypt silently truncates passwords at 72 bytes, so passwords longer than 72
characters provide no additional security. This commit enforces the 72-byte
maximum across the authentication and setup flows.

Changes:
- Add max_length=72 to LoginRequest.password and SetupRequest.master_password
- Update field validator in SetupRequest to explicitly check max_length
- Add comprehensive tests for password length validation (6 new test cases)
- Document the 72-byte limitation in Features.md (master password options)
- Add new section 12 'Password Hashing' in Backend-Development.md explaining:
  - The bcrypt truncation behavior
  - Why the limit is enforced
  - The validation flow from frontend to backend
  - What happens when passwords exceed the limit

All existing tests pass, no regressions introduced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-26 15:38:20 +02:00
parent 1d91e24a88
commit 32aad186c3
5 changed files with 121 additions and 7 deletions

View File

@@ -11,7 +11,11 @@ class LoginRequest(BaseModel):
model_config = ConfigDict(strict=True)
password: str = Field(..., description="Master password to authenticate with.")
password: str = Field(
...,
max_length=72,
description="Master password to authenticate with (max 72 bytes due to bcrypt truncation).",
)
class LoginResponse(BaseModel):

View File

@@ -14,7 +14,8 @@ class SetupRequest(BaseModel):
master_password: str = Field(
...,
min_length=8,
description="Master password that protects the BanGUI interface.",
max_length=72,
description="Master password that protects the BanGUI interface (max 72 bytes due to bcrypt truncation).",
)
@field_validator("master_password")
@@ -22,6 +23,8 @@ class SetupRequest(BaseModel):
def validate_master_password(cls, value: str) -> str:
if len(value) < 8:
raise ValueError("Password must be at least 8 characters long.")
if len(value) > 72:
raise ValueError("Password must not exceed 72 bytes (bcrypt limitation).")
if not any(char.isupper() for char in value):
raise ValueError("Password must include at least one uppercase letter.")
if not any(char.isdigit() for char in value):