147 lines
5.0 KiB
Markdown
147 lines
5.0 KiB
Markdown
# Story 1.3: User Authentication System - COMPLETED
|
|
|
|
## Overview
|
|
Implemented a secure user authentication system that validates user credentials and identifies roles.
|
|
|
|
## Story Details
|
|
**As an Admin**, I want a secure system for user login, so that only authenticated users can access the application's functions.
|
|
|
|
## Acceptance Criteria - ALL MET
|
|
|
|
### 1. Password Hashing Function
|
|
**Status:** COMPLETE
|
|
|
|
A secure password hashing function exists using bcrypt via passlib:
|
|
- Location: `src/auth/service.py`
|
|
- Method: `AuthService.hash_password(password: str) -> str`
|
|
- Uses bcrypt algorithm for secure password hashing
|
|
- Each hash includes a unique salt for security
|
|
|
|
### 2. Authentication Mechanism
|
|
**Status:** COMPLETE
|
|
|
|
An authentication mechanism validates username and password:
|
|
- Location: `src/auth/service.py`
|
|
- Method: `AuthService.authenticate_user(username: str, password: str) -> Optional[User]`
|
|
- Returns User object on successful authentication
|
|
- Returns None on failed authentication
|
|
- Verifies password against stored hash using bcrypt
|
|
|
|
### 3. Role Identification
|
|
**Status:** COMPLETE
|
|
|
|
Upon successful login, the system identifies user roles:
|
|
- User model includes `role` field ("Admin" or "User")
|
|
- User model provides `is_admin()` helper method
|
|
- Authentication returns full User object with role information
|
|
- Roles are preserved and accessible throughout the session
|
|
|
|
### 4. Authentication Failure Handling
|
|
**Status:** COMPLETE
|
|
|
|
Authentication correctly fails for incorrect credentials:
|
|
- Returns None for nonexistent usernames
|
|
- Returns None for incorrect passwords
|
|
- Secure timing to prevent enumeration attacks (via bcrypt)
|
|
|
|
## Implementation Details
|
|
|
|
### Files Created/Modified
|
|
|
|
#### 1. `src/auth/service.py` - NEW
|
|
```python
|
|
class AuthService:
|
|
- hash_password(password: str) -> str
|
|
- verify_password(plain_password: str, hashed_password: str) -> bool
|
|
- authenticate_user(username: str, password: str) -> Optional[User]
|
|
- create_user_with_hashed_password(username: str, password: str, role: str) -> User
|
|
```
|
|
|
|
#### 2. `src/database/models.py` - UPDATED
|
|
- Fixed deprecation warning: Updated datetime.utcnow() to datetime.now(timezone.utc)
|
|
- Model already includes role field and is_admin() method (from Story 1.2)
|
|
|
|
#### 3. `requirements.txt` - UPDATED
|
|
- Added bcrypt version pin for compatibility with passlib
|
|
|
|
### Test Coverage
|
|
|
|
#### Unit Tests (`tests/unit/test_auth_service.py`)
|
|
- 10 test cases covering all authentication scenarios
|
|
- Tests password hashing, verification, and authentication flows
|
|
- Tests both Admin and User role identification
|
|
- All tests passing
|
|
|
|
#### Integration Tests (`tests/integration/test_auth_integration.py`)
|
|
- 5 integration test cases with real database
|
|
- Tests full authentication flow from user creation to login
|
|
- Tests multiple users with different roles
|
|
- Verifies passwords are properly hashed in database
|
|
- All tests passing
|
|
|
|
## Security Features
|
|
|
|
1. **Bcrypt Password Hashing**: Industry-standard password hashing with automatic salt generation
|
|
2. **No Plain-Text Passwords**: Passwords are never stored in plain text
|
|
3. **Constant-Time Comparison**: Bcrypt provides timing-attack resistance
|
|
4. **Role-Based Access**: Clear separation between Admin and User roles
|
|
5. **Secure Defaults**: All authentication failures return None (no information leakage)
|
|
|
|
## Dependencies
|
|
- `passlib[bcrypt]==1.7.4` - Password hashing library
|
|
- `bcrypt==4.0.1` - Bcrypt implementation (version pinned for compatibility)
|
|
|
|
## Usage Example
|
|
|
|
```python
|
|
from src.database.session import get_session
|
|
from src.database.repositories import UserRepository
|
|
from src.auth.service import AuthService
|
|
|
|
# Initialize services
|
|
session = get_session()
|
|
user_repo = UserRepository(session)
|
|
auth_service = AuthService(user_repo)
|
|
|
|
# Create a user with hashed password
|
|
user = auth_service.create_user_with_hashed_password(
|
|
username="admin",
|
|
password="secure_password",
|
|
role="Admin"
|
|
)
|
|
|
|
# Authenticate user
|
|
authenticated = auth_service.authenticate_user("admin", "secure_password")
|
|
if authenticated:
|
|
print(f"Welcome {authenticated.username}!")
|
|
if authenticated.is_admin():
|
|
print("You have admin privileges")
|
|
else:
|
|
print("Authentication failed")
|
|
```
|
|
|
|
## Testing Commands
|
|
|
|
```bash
|
|
# Run unit tests
|
|
.venv/Scripts/python -m pytest tests/unit/test_auth_service.py -v
|
|
|
|
# Run integration tests
|
|
.venv/Scripts/python -m pytest tests/integration/test_auth_integration.py -v
|
|
|
|
# Run all auth tests
|
|
.venv/Scripts/python -m pytest tests/unit/test_auth_service.py tests/integration/test_auth_integration.py -v
|
|
```
|
|
|
|
## Next Steps
|
|
This authentication system is now ready to be integrated into:
|
|
- Story 1.4: Internal API Foundation (API endpoint authentication)
|
|
- Story 1.5: Command-Line User Management (CLI authentication)
|
|
- Future features requiring user authentication
|
|
|
|
## Notes
|
|
- The implementation uses repository pattern for clean separation of concerns
|
|
- AuthService is stateless and can be safely instantiated multiple times
|
|
- The system is designed to be extensible for future authentication methods (tokens, sessions, etc.)
|
|
|