# Path: bitsd/persistence/query.py
# def get_user(session, username):
#     """Get user by name."""
#     if not username:
#         return None
#     return query_by_attribute(session, User, 'name', username)
# 
# def get_last_login_attempt(session, ip_address, username=None):
#     """Get last failed login attempt for given IP address. Filter by username if provided"""
#     L = LoginAttempt  # Shortcut
#     if username is not None:
#         return session.query(L).filter(L.ipaddress == ip_address, L.username == username).order_by(desc(L.timestamp)).first()
#     else:
#         return session.query(L).filter(L.ipaddress == ip_address).order_by(desc(L.timestamp)).first()
# 
# def log_last_login_attempt(session, ip_address, username):
#     """"Persist failed login attempt (either insert or update)"""
#     attempt = LoginAttempt(username, ip_address)
#     attempt = session.merge(attempt)
#     return persist(session, attempt)
# 
# Path: bitsd/persistence/models.py
# class User(Base):
#     """User name/password hash entity."""
#     __tablename__ = 'User'
#     __table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8mb4'}
# 
#     userid = Column(Integer, primary_key=True)
#     name = Column(String(length=256), unique=True, nullable=False)
#     password = Column(String(length=512), nullable=False)
# 
#     def __init__(self, name, pwhash):
#         self.name = name
#         self.password = pwhash
# 
#     def __str__(self):
#         return '{self.name}: {self.password}'.format(self=self)
# 
# Path: bitsd/persistence/engine.py
# def persist(session, data, flush=True):
#     """Persist data to configured DB and return persisted object
#     in a consistent state.
# 
#     **Note:** might log what's being persisted, so don't put clear text password
#     into `__str__()`."""
#     LOG.debug('Persisting data %r', data)
#     session.add(data)
#     if flush:
#         session.flush()
#     return data
# 
# def delete(session, data):
#     """Delete data from DB."""
#     LOG.debug('Deleting %r', data)
#     session.delete(data)
# 
# Path: bitsd/common.py
# LOG = logging.getLogger('tornado.general')


from datetime import datetime, timedelta
from passlib.hash import sha512_crypt as Hasher
from tornado.options import options
from recaptcha import RecaptchaClient
from bitsd.persistence.query import get_user, get_last_login_attempt, log_last_login_attempt
from bitsd.persistence.models import User
from bitsd.persistence.engine import persist, delete
from bitsd.common import LOG

    A DoS protection is necessary because password hashing is an expensive operation.
    """
    if has_captcha:
        solved_captcha = ReCaptcha.is_solution_correct(recaptcha_response, recaptcha_challenge, ip_address)
        # Exit immediately if wrong answer
        if not solved_captcha:
            return False
    else:
        solved_captcha = False

    # Save "now" so that the two timestamp checks are referred to the same instant
    now = datetime.now()

    def detect_dos(attempt, timeout):
        if solved_captcha:
            return False

        # Otherwise, check timing
        if attempt is not None:
            too_quick = (now - attempt.timestamp) < timeout
            if too_quick:
                log_last_login_attempt(session, ip_address, username)
                return True
            else:
                # Clean up if no more relevant
                session.delete(attempt)
        return False

    last_attempt_for_ip = get_last_login_attempt(session, ip_address)

# Path: bitsd/persistence/query.py
# def get_user(session, username):
#     """Get user by name."""
#     if not username:
#         return None
#     return query_by_attribute(session, User, 'name', username)
# 
# def get_last_login_attempt(session, ip_address, username=None):
#     """Get last failed login attempt for given IP address. Filter by username if provided"""
#     L = LoginAttempt  # Shortcut
#     if username is not None:
#         return session.query(L).filter(L.ipaddress == ip_address, L.username == username).order_by(desc(L.timestamp)).first()
#     else:
#         return session.query(L).filter(L.ipaddress == ip_address).order_by(desc(L.timestamp)).first()
# 
# def log_last_login_attempt(session, ip_address, username):
#     """"Persist failed login attempt (either insert or update)"""
#     attempt = LoginAttempt(username, ip_address)
#     attempt = session.merge(attempt)
#     return persist(session, attempt)
# 
# Path: bitsd/persistence/models.py
# class User(Base):
#     """User name/password hash entity."""
#     __tablename__ = 'User'
#     __table_args__ = {'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8mb4'}
# 
#     userid = Column(Integer, primary_key=True)
#     name = Column(String(length=256), unique=True, nullable=False)
#     password = Column(String(length=512), nullable=False)
# 
#     def __init__(self, name, pwhash):
#         self.name = name
#         self.password = pwhash
# 
#     def __str__(self):
#         return '{self.name}: {self.password}'.format(self=self)
# 
# Path: bitsd/persistence/engine.py
# def persist(session, data, flush=True):
#     """Persist data to configured DB and return persisted object
#     in a consistent state.
# 
#     **Note:** might log what's being persisted, so don't put clear text password
#     into `__str__()`."""
#     LOG.debug('Persisting data %r', data)
#     session.add(data)
#     if flush:
#         session.flush()
#     return data
# 
# def delete(session, data):
#     """Delete data from DB."""
#     LOG.debug('Deleting %r', data)
#     session.delete(data)
# 
# Path: bitsd/common.py
# LOG = logging.getLogger('tornado.general')


# Path: bitsd/server/auth.py
from datetime import datetime, timedelta
from passlib.hash import sha512_crypt as Hasher
from tornado.options import options
from recaptcha import RecaptchaClient
from bitsd.persistence.query import get_user, get_last_login_attempt, log_last_login_attempt
from bitsd.persistence.models import User
from bitsd.persistence.engine import persist, delete
from bitsd.common import LOG


    A DoS protection is necessary because password hashing is an expensive operation.
    """
    if has_captcha:
        solved_captcha = ReCaptcha.is_solution_correct(recaptcha_response, recaptcha_challenge, ip_address)
        # Exit immediately if wrong answer
        if not solved_captcha:
            return False
    else:
        solved_captcha = False

    # Save "now" so that the two timestamp checks are referred to the same instant
    now = datetime.now()

    def detect_dos(attempt, timeout):
        if solved_captcha:
            return False

        # Otherwise, check timing
        if attempt is not None:
            too_quick = (now - attempt.timestamp) < timeout
            if too_quick:
                log_last_login_attempt(session, ip_address, username)
                return True
            else:
                # Clean up if no more relevant
                session.delete(attempt)
        return False

    last_attempt_for_ip = get_last_login_attempt(session, ip_address)

    last_attempt_for_ip_and_username = get_last_login_attempt(session, ip_address, username)