Andrei Neagoie Python Link
def __init__( self, secret_key: str, max_failed_attempts: int = 5, lockout_minutes: int = 15 ): """ Initialize authentication service Args: secret_key: Secret key for JWT max_failed_attempts: Number of failed attempts before lockout lockout_minutes: Lockout duration in minutes """ self.users: Dict[str, User] = {} self.token_manager = TokenManager(secret_key) self.password_hasher = PasswordHasher() self.rate_limiter = RateLimiter() self.max_failed_attempts = max_failed_attempts self.lockout_minutes = lockout_minutes
def validate_token(self, token: str) -> Dict: """ Validate and decode JWT token Args: token: JWT token string Returns: Decoded token payload Raises: AuthenticationError: If token is invalid or expired """ try: payload = jwt.decode( token, self.secret_key, algorithms=['HS256'] ) return payload except ExpiredSignatureError: raise AuthenticationError("Token has expired") except InvalidTokenError as e: raise AuthenticationError(f"Invalid token: str(e)") class RateLimiter: """Simple in-memory rate limiter for authentication attempts""" andrei neagoie python
def check_rate_limit(self, key: str) -> bool: """ Check if rate limit is exceeded for given key Args: key: Identifier for rate limiting (e.g., email or IP) Returns: True if under limit, False if exceeded Raises: RateLimitExceededError: If rate limit is exceeded """ now = time.time() # Clean up old attempts if key in self.attempts: self.attempts[key] = [ attempt_time for attempt_time in self.attempts[key] if now - attempt_time < self.window_seconds ] # Check if limit exceeded if len(self.attempts.get(key, [])) >= self.max_attempts: wait_time = self.window_seconds - (now - self.attempts[key][0]) raise RateLimitExceededError( f"Too many attempts. Please try again in int(wait_time) seconds" ) return True def __init__( self
def test_verify_wrong_password(self): hasher = PasswordHasher() hashed = hasher.hash_password("Correct123!") assert not hasher.verify_password("Wrong456!", hashed) class TestAuthenticationService: @pytest.fixture def auth_service(self): return AuthenticationService(secret_key="test-secret-key-123") max_failed_attempts: int = 5
@staticmethod def _validate_password_strength(password: str) -> None: """ Validate password meets security requirements Requirements: - Minimum 8 characters - At least 1 uppercase letter - At least 1 lowercase letter - At least 1 digit - At least 1 special character Raises: ValidationError: If password doesn't meet requirements """ if len(password) < 8: raise ValidationError("Password must be at least 8 characters long") if not re.search(r'[A-Z]', password): raise ValidationError("Password must contain at least one uppercase letter") if not re.search(r'[a-z]', password): raise ValidationError("Password must contain at least one lowercase letter") if not re.search(r'\d', password): raise ValidationError("Password must contain at least one digit") if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password): raise ValidationError("Password must contain at least one special character") class TokenManager: """Handles JWT token creation and validation"""
def test_account_lockout(self, auth_service): auth_service.register_user("test@example.com", "ValidPass123!") # Try wrong password 5 times (max_failed_attempts=5) for _ in range(5): with pytest.raises(InvalidPasswordError): auth_service.login("test@example.com", "wrong", "127.0.0.1") # Next attempt should lock account with pytest.raises(AuthenticationError, match="Account locked"): auth_service.login("test@example.com", "ValidPass123!", "127.0.0.1")
@staticmethod def hash_password(password: str) -> str: """ Hash password using SHA-256 with salt Args: password: Plain text password Returns: String containing salt and hash separated by colon Raises: ValidationError: If password doesn't meet security requirements """ PasswordHasher._validate_password_strength(password) # Generate random salt (32 bytes) salt = os.urandom(32) # Hash password with salt password_hash = hashlib.pbkdf2_hmac( 'sha256', password.encode('utf-8'), salt, 100000 # Number of iterations ) # Return salt and hash as hex strings return f"salt.hex():password_hash.hex()"