Compare commits
2 Commits
1f786a83ce
...
e9dcf8b5dc
| Author | SHA1 | Date | |
|---|---|---|---|
| e9dcf8b5dc | |||
|
|
31c42ca7ae |
22
main.py
22
main.py
@@ -6,6 +6,7 @@ Main application entry point for the sandbox project.
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from src.auth import UserManager
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
"""Load configuration from config.json"""
|
"""Load configuration from config.json"""
|
||||||
@@ -24,6 +25,27 @@ def main():
|
|||||||
|
|
||||||
if config.get("debug"):
|
if config.get("debug"):
|
||||||
print("Running in debug mode")
|
print("Running in debug mode")
|
||||||
|
|
||||||
|
# Initialize authentication system
|
||||||
|
auth_manager = UserManager()
|
||||||
|
print("\n--- Authentication System Demo ---")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Register a demo user
|
||||||
|
auth_manager.register_user("demo_user", "demo@example.com", "secure_password123")
|
||||||
|
print("✓ Demo user registered successfully")
|
||||||
|
|
||||||
|
# Authenticate the user
|
||||||
|
session_token = auth_manager.authenticate("demo_user", "secure_password123")
|
||||||
|
print("✓ User authenticated successfully")
|
||||||
|
print(f"Session token: {session_token[:20]}...")
|
||||||
|
|
||||||
|
# Validate session
|
||||||
|
username = auth_manager.validate_session(session_token)
|
||||||
|
print(f"✓ Session validated for user: {username}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Authentication error: {e}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
120
src/auth.py
Normal file
120
src/auth.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
"""
|
||||||
|
User authentication module for the sandbox project
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import secrets
|
||||||
|
import time
|
||||||
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
|
class User:
|
||||||
|
"""Represents a user in the system"""
|
||||||
|
|
||||||
|
def __init__(self, username: str, email: str, password_hash: str):
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.password_hash = password_hash
|
||||||
|
self.created_at = time.time()
|
||||||
|
self.last_login = None
|
||||||
|
self.is_active = True
|
||||||
|
|
||||||
|
class AuthenticationError(Exception):
|
||||||
|
"""Raised when authentication fails"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UserManager:
|
||||||
|
"""Manages user authentication and registration"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.users: Dict[str, User] = {}
|
||||||
|
self.sessions: Dict[str, str] = {} # session_token -> username
|
||||||
|
|
||||||
|
def _hash_password(self, password: str, salt: str = None) -> Tuple[str, str]:
|
||||||
|
"""Hash a password with salt"""
|
||||||
|
if salt is None:
|
||||||
|
salt = secrets.token_hex(16)
|
||||||
|
|
||||||
|
password_hash = hashlib.pbkdf2_hmac(
|
||||||
|
'sha256',
|
||||||
|
password.encode('utf-8'),
|
||||||
|
salt.encode('utf-8'),
|
||||||
|
100000 # iterations
|
||||||
|
)
|
||||||
|
|
||||||
|
return password_hash.hex(), salt
|
||||||
|
|
||||||
|
def register_user(self, username: str, email: str, password: str) -> bool:
|
||||||
|
"""Register a new user"""
|
||||||
|
if username in self.users:
|
||||||
|
raise ValueError("Username already exists")
|
||||||
|
|
||||||
|
if len(password) < 8:
|
||||||
|
raise ValueError("Password must be at least 8 characters long")
|
||||||
|
|
||||||
|
password_hash, salt = self._hash_password(password)
|
||||||
|
full_hash = f"{salt}:{password_hash}"
|
||||||
|
|
||||||
|
user = User(username, email, full_hash)
|
||||||
|
self.users[username] = user
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def authenticate(self, username: str, password: str) -> Optional[str]:
|
||||||
|
"""Authenticate a user and return session token"""
|
||||||
|
if username not in self.users:
|
||||||
|
raise AuthenticationError("Invalid username or password")
|
||||||
|
|
||||||
|
user = self.users[username]
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise AuthenticationError("Account is deactivated")
|
||||||
|
|
||||||
|
# Extract salt and hash from stored password
|
||||||
|
try:
|
||||||
|
salt, stored_hash = user.password_hash.split(':', 1)
|
||||||
|
except ValueError:
|
||||||
|
raise AuthenticationError("Invalid password format")
|
||||||
|
|
||||||
|
# Hash the provided password with the stored salt
|
||||||
|
password_hash, _ = self._hash_password(password, salt)
|
||||||
|
|
||||||
|
if password_hash != stored_hash:
|
||||||
|
raise AuthenticationError("Invalid username or password")
|
||||||
|
|
||||||
|
# Generate session token
|
||||||
|
session_token = secrets.token_urlsafe(32)
|
||||||
|
self.sessions[session_token] = username
|
||||||
|
|
||||||
|
# Update last login
|
||||||
|
user.last_login = time.time()
|
||||||
|
|
||||||
|
return session_token
|
||||||
|
|
||||||
|
def validate_session(self, session_token: str) -> Optional[str]:
|
||||||
|
"""Validate a session token and return username"""
|
||||||
|
return self.sessions.get(session_token)
|
||||||
|
|
||||||
|
def logout(self, session_token: str) -> bool:
|
||||||
|
"""Logout a user by invalidating their session"""
|
||||||
|
if session_token in self.sessions:
|
||||||
|
del self.sessions[session_token]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_user(self, username: str) -> Optional[User]:
|
||||||
|
"""Get user information"""
|
||||||
|
return self.users.get(username)
|
||||||
|
|
||||||
|
def deactivate_user(self, username: str) -> bool:
|
||||||
|
"""Deactivate a user account"""
|
||||||
|
if username in self.users:
|
||||||
|
self.users[username].is_active = False
|
||||||
|
# Remove all sessions for this user
|
||||||
|
sessions_to_remove = [
|
||||||
|
token for token, user in self.sessions.items()
|
||||||
|
if user == username
|
||||||
|
]
|
||||||
|
for token in sessions_to_remove:
|
||||||
|
del self.sessions[token]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
141
tests/unit/test_auth.py
Normal file
141
tests/unit/test_auth.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import pytest
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Add src directory to path for imports
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))
|
||||||
|
|
||||||
|
from auth import UserManager, AuthenticationError, User
|
||||||
|
|
||||||
|
class TestUserManager:
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
"""Setup test fixtures"""
|
||||||
|
self.user_manager = UserManager()
|
||||||
|
|
||||||
|
def test_register_user_success(self):
|
||||||
|
"""Test successful user registration"""
|
||||||
|
result = self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
assert result is True
|
||||||
|
assert "testuser" in self.user_manager.users
|
||||||
|
|
||||||
|
user = self.user_manager.users["testuser"]
|
||||||
|
assert user.username == "testuser"
|
||||||
|
assert user.email == "test@example.com"
|
||||||
|
assert user.is_active is True
|
||||||
|
assert user.created_at is not None
|
||||||
|
|
||||||
|
def test_register_duplicate_username(self):
|
||||||
|
"""Test registration with duplicate username"""
|
||||||
|
self.user_manager.register_user("testuser", "test1@example.com", "password123")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Username already exists"):
|
||||||
|
self.user_manager.register_user("testuser", "test2@example.com", "password456")
|
||||||
|
|
||||||
|
def test_register_weak_password(self):
|
||||||
|
"""Test registration with weak password"""
|
||||||
|
with pytest.raises(ValueError, match="Password must be at least 8 characters long"):
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "123")
|
||||||
|
|
||||||
|
def test_authenticate_success(self):
|
||||||
|
"""Test successful authentication"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
|
||||||
|
session_token = self.user_manager.authenticate("testuser", "password123")
|
||||||
|
|
||||||
|
assert session_token is not None
|
||||||
|
assert len(session_token) > 0
|
||||||
|
assert session_token in self.user_manager.sessions
|
||||||
|
assert self.user_manager.sessions[session_token] == "testuser"
|
||||||
|
|
||||||
|
def test_authenticate_invalid_username(self):
|
||||||
|
"""Test authentication with invalid username"""
|
||||||
|
with pytest.raises(AuthenticationError, match="Invalid username or password"):
|
||||||
|
self.user_manager.authenticate("nonexistent", "password123")
|
||||||
|
|
||||||
|
def test_authenticate_invalid_password(self):
|
||||||
|
"""Test authentication with invalid password"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
|
||||||
|
with pytest.raises(AuthenticationError, match="Invalid username or password"):
|
||||||
|
self.user_manager.authenticate("testuser", "wrongpassword")
|
||||||
|
|
||||||
|
def test_authenticate_deactivated_user(self):
|
||||||
|
"""Test authentication with deactivated user"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
self.user_manager.deactivate_user("testuser")
|
||||||
|
|
||||||
|
with pytest.raises(AuthenticationError, match="Account is deactivated"):
|
||||||
|
self.user_manager.authenticate("testuser", "password123")
|
||||||
|
|
||||||
|
def test_validate_session_success(self):
|
||||||
|
"""Test successful session validation"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
session_token = self.user_manager.authenticate("testuser", "password123")
|
||||||
|
|
||||||
|
username = self.user_manager.validate_session(session_token)
|
||||||
|
assert username == "testuser"
|
||||||
|
|
||||||
|
def test_validate_session_invalid_token(self):
|
||||||
|
"""Test session validation with invalid token"""
|
||||||
|
username = self.user_manager.validate_session("invalid_token")
|
||||||
|
assert username is None
|
||||||
|
|
||||||
|
def test_logout_success(self):
|
||||||
|
"""Test successful logout"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
session_token = self.user_manager.authenticate("testuser", "password123")
|
||||||
|
|
||||||
|
result = self.user_manager.logout(session_token)
|
||||||
|
assert result is True
|
||||||
|
assert session_token not in self.user_manager.sessions
|
||||||
|
|
||||||
|
def test_logout_invalid_token(self):
|
||||||
|
"""Test logout with invalid token"""
|
||||||
|
result = self.user_manager.logout("invalid_token")
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
def test_get_user_success(self):
|
||||||
|
"""Test getting user information"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
|
||||||
|
user = self.user_manager.get_user("testuser")
|
||||||
|
assert user is not None
|
||||||
|
assert user.username == "testuser"
|
||||||
|
assert user.email == "test@example.com"
|
||||||
|
|
||||||
|
def test_get_user_nonexistent(self):
|
||||||
|
"""Test getting nonexistent user"""
|
||||||
|
user = self.user_manager.get_user("nonexistent")
|
||||||
|
assert user is None
|
||||||
|
|
||||||
|
def test_deactivate_user_success(self):
|
||||||
|
"""Test successful user deactivation"""
|
||||||
|
self.user_manager.register_user("testuser", "test@example.com", "password123")
|
||||||
|
session_token = self.user_manager.authenticate("testuser", "password123")
|
||||||
|
|
||||||
|
result = self.user_manager.deactivate_user("testuser")
|
||||||
|
assert result is True
|
||||||
|
assert self.user_manager.users["testuser"].is_active is False
|
||||||
|
assert session_token not in self.user_manager.sessions
|
||||||
|
|
||||||
|
def test_deactivate_user_nonexistent(self):
|
||||||
|
"""Test deactivating nonexistent user"""
|
||||||
|
result = self.user_manager.deactivate_user("nonexistent")
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
def test_password_hashing_security(self):
|
||||||
|
"""Test that passwords are properly hashed and salted"""
|
||||||
|
self.user_manager.register_user("user1", "user1@example.com", "password123")
|
||||||
|
self.user_manager.register_user("user2", "user2@example.com", "password123")
|
||||||
|
|
||||||
|
user1 = self.user_manager.users["user1"]
|
||||||
|
user2 = self.user_manager.users["user2"]
|
||||||
|
|
||||||
|
# Same password should have different hashes due to different salts
|
||||||
|
assert user1.password_hash != user2.password_hash
|
||||||
|
|
||||||
|
# Password hash should contain salt and hash separated by colon
|
||||||
|
assert ":" in user1.password_hash
|
||||||
|
assert ":" in user2.password_hash
|
||||||
Reference in New Issue
Block a user