|
- import web
- from views.forms import login_form
- from views.utils import get_nav_bar, csrf_protected, get_render, check_password, get_totp_token
- import models.session
- import models.user
- import logging
- import random
- import string
- import time
-
- logger = logging.getLogger(__name__)
-
- # The remember cookie should be valid for a week
- remember_timeout = 3600*24*7
-
- # The timeout between login attempts, after the 3rd incorrect one
- login_timeout = 60
- login_attempts_threshold = 2
-
-
- class Login():
-
- # Get the server secret to perform signatures
- secret = web.config.get('session_parameters')['secret_key']
-
- def GET(self):
- """
- Show the login page
-
- :return: The login page showing other users if logged in
- """
- session = web.ctx.session
- nav = get_nav_bar(session)
-
- # Log the user in if the rememberme cookie is set and valid
- self.check_rememberme()
-
- return get_render().login(nav, login_form, "")
-
- @csrf_protected
- def POST(self):
- """
- Log in to the web application and register the session
- :return: The login page showing other users if logged in
- """
- session = web.ctx.session
- nav = get_nav_bar(session)
- data = web.input(username="", password="", remember=False)
- render = get_render()
-
- # Validate login credential with database query
- user = models.user.get_user(data.username)
-
- if user is None:
- return render.login(nav, login_form, "- User authentication failed")
-
- if user["login_attempts"] > login_attempts_threshold and user["last_login_attempt"] + login_timeout > time.time():
- return render.login(nav, login_form, "- There have been too many incorrect login attempts for your account. You have to wait a minute before you can log in.")
-
- two_factor = get_totp_token(user["authenticator_secret"]) == data.authenticator_secret
-
- if two_factor and check_password(data.password, user["password"]):
- if user["login_attempts"] > login_attempts_threshold:
- logger.info("User %s logged in succesfully after %s attempts", user["username"], user["login_attempts"])
-
- if not models.user.is_verified(user["userid"]):
- return render.login(nav, login_form, "- User not authenticated yet. Please check you email.")
-
- if user["temporary_password"]:
- logger.info("A password reset was requested for user %s, but they logged in with the old password before resetting")
- models.user.set_temporary_password(user["userid"], "")
-
- models.user.set_login_attempts(user["userid"], 0, time.time())
- self.login(user["username"], user["userid"], data.remember)
- raise web.seeother("/")
- elif two_factor and check_password(data.password, user["temporary_password"]):
- session.temporary_userid = user["userid"]
- raise web.seeother("/reset")
- else:
- logger.warning("Incorrect login attempt on user %s by IP %s", user["username"], web.ctx.ip)
- models.user.set_login_attempts(user["userid"], user["login_attempts"]+1, time.time())
- if user["login_attempts"] == login_attempts_threshold:
- return render.login(nav, login_form, "- Too many incorrect login attempts. You have to wait a minute before trying again.")
- else:
- return render.login(nav, login_form, "- User authentication failed")
-
- def login(self, username, userid, remember):
- """
- Log in to the application
- """
- session = web.ctx.session
- session.username = username
- session.userid = userid
- if remember:
- rememberme = self.rememberme(remember_timeout)
- path = web.ctx.homepath + "/"
- web.ctx.headers.append(('Set-Cookie', f'remember={rememberme}; Max-Age={remember_timeout}; Path={path}; Secure; HttpOnly; SameSite=Strict'))
-
- def check_rememberme(self):
- """
- Validate the rememberme cookie and log in
- """
- userid = None
- # If the user selected 'remember me' they log in automatically
- try:
- # Fetch the users cookies if it exists
- cookies = web.cookies()
- # Fetch the remember cookie and convert from string to bytes
- remember_token = cookies.remember
- userid, expiry = models.session.get_cookie(remember_token)
- except AttributeError:
- # The user did not have the stored remember me cookie
- pass
-
- # If the users signed cookie matches the host signature then log in
- if userid is not None and expiry > time.time():
- username = models.user.get_user_name_by_id(userid)
- self.login(username, userid, False)
-
- def rememberme(self, timeout):
- """
- Generate a random token for the user, and store it in the database.
- """
- session = web.ctx.session
- alphabet = string.ascii_uppercase + string.digits
-
- while True:
- token = ''.join(random.SystemRandom().choice(alphabet) for _ in range(20))
- if models.session.get_cookie(token)[0] is None:
- break
-
- models.session.set_cookie(session.userid, token, int(time.time() + timeout))
- return token
|