def post_register( request: Request, username: str, password: str, password_confirm: str, invite_code: str, ) -> HTTPFound: """Process a registration request.""" if not request.params.get("accepted_terms"): raise HTTPUnprocessableEntity( "Terms of Use and Privacy Policy must be accepted.") if password != password_confirm: raise HTTPUnprocessableEntity( "Password and confirmation do not match.") code_row = _handle_invite_code(request, invite_code) # create the user and set inviter to the owner of the invite code user = User(username, password) if code_row: user.inviter_id = code_row.user_id # flush the user insert to db, will fail if username is already taken request.db_session.add(user) try: request.db_session.flush() except IntegrityError: raise HTTPUnprocessableEntity( "That username has already been registered.") # the flush above will generate the new user's ID, so use that to update the invite # code with info about the user that registered with it if code_row: code_row.invitee_id = user.user_id # subscribe the new user to all groups except ~test all_groups = request.query(Group).all() for group in all_groups: if group.path == "test": continue request.db_session.add(GroupSubscription(user, group)) _send_welcome_message(user, request) incr_counter("registrations") # log the user in to the new account remember(request, user.user_id) # set request.user before logging so the user is associated with the event request.user = user request.db_session.add(Log(LogEventType.USER_REGISTER, request)) # redirect to the front page raise HTTPFound(location="/")
def post_login(request: Request, username: str, password: str) -> HTTPFound: """Process a log in request.""" incr_counter('logins') # Look up the user for the supplied username user = (request.query(User).undefer_all_columns().filter( User.username == username).one_or_none()) # If that user doesn't exist or the password was wrong, error out if not user or not user.is_correct_password(password): incr_counter('login_failures') # log the failure - need to manually commit because of the exception log_entry = Log(LogEventType.USER_LOG_IN_FAIL, request, {'username': username}) request.db_session.add(log_entry) request.tm.commit() raise HTTPUnprocessableEntity('Incorrect username or password') # Don't allow banned users to log in if user.is_banned: raise HTTPUnprocessableEntity('This account has been banned') # Username/password were correct - attach the user_id to the session remember(request, user.user_id) # Depending on "keep me logged in", set session timeout to 1 year or 1 day if request.params.get('keep'): request.session.adjust_timeout_for_session(31_536_000) else: request.session.adjust_timeout_for_session(86_400) # set request.user before logging so the user is associated with the event request.user = user request.db_session.add(Log(LogEventType.USER_LOG_IN, request)) raise HTTPFound(location='/')
def finish_login(request: Request, user: User, redirect_url: str) -> HTTPFound: """Save the user ID into session and return a redirect to appropriate page.""" # Username/password were correct - attach the user_id to the session remember(request, user.user_id) # Depending on "keep me logged in", set session timeout to 1 year or 1 day if request.params.get("keep"): request.session.adjust_timeout_for_session(31_536_000) else: request.session.adjust_timeout_for_session(86400) # set request.user before logging so the user is associated with the event request.user = user request.db_session.add(Log(LogEventType.USER_LOG_IN, request)) # only use redirect_url if it's a relative url, so we can't redirect to other sites if redirect_url.startswith("/"): return HTTPFound(location=redirect_url) return HTTPFound(location="/")
def patch_change_email_address(request: Request, email_address: str, email_address_note: str) -> Response: """Change the user's email address (and descriptive note).""" user = request.context # If the user already has an email address set, we need to retain the previous hash # and description in the log. Otherwise, if an account is compromised and the # attacker changes the email address, we'd have no way to support recovery for the # owner. log_info = None if user.email_address_hash: log_info = { "old_hash": user.email_address_hash, "old_note": user.email_address_note, } request.db_session.add(Log(LogEventType.USER_EMAIL_SET, request, log_info)) user.email_address = email_address user.email_address_note = email_address_note return Response("Your email address has been updated")
def post_login( request: Request, username: str, password: str, from_url: str ) -> Response: """Process a log in request.""" incr_counter("logins") # Look up the user for the supplied username user = ( request.query(User) .undefer_all_columns() .filter(User.username == username) .one_or_none() ) # If that user doesn't exist or the password was wrong, error out if not user or not user.is_correct_password(password): incr_counter("login_failures") # log the failure - need to manually commit because of the exception log_entry = Log(LogEventType.USER_LOG_IN_FAIL, request, {"username": username}) request.db_session.add(log_entry) request.tm.commit() raise HTTPUnprocessableEntity("Incorrect username or password") # Don't allow banned users to log in if user.is_banned: raise HTTPUnprocessableEntity("This account has been banned") # If 2FA is enabled, save username to session and make user enter code if user.two_factor_enabled: request.session["two_factor_username"] = username return render_to_response( "tildes:templates/intercooler/login_two_factor.jinja2", {"keep": request.params.get("keep"), "from_url": from_url}, request=request, ) raise finish_login(request, user, from_url)
def post_logout(request: Request) -> HTTPFound: """Process a log out request.""" request.session.invalidate() request.db_session.add(Log(LogEventType.USER_LOG_OUT, request)) raise HTTPFound(location="/")
def post_register( request: Request, username: str, password: str, password_confirm: str, invite_code: str, ) -> HTTPFound: """Process a registration request.""" if not request.params.get("accepted_terms"): raise HTTPUnprocessableEntity( "Terms of Use and Privacy Policy must be accepted.") if password != password_confirm: raise HTTPUnprocessableEntity( "Password and confirmation do not match.") # attempt to fetch and lock the row for the specified invite code (lock prevents # concurrent requests from using the same invite code) lookup_code = UserInviteCode.prepare_code_for_lookup(invite_code) code_row = ( request.query(UserInviteCode).filter( UserInviteCode.code == lookup_code, UserInviteCode.invitee_id == None, # noqa ).with_for_update(skip_locked=True).one_or_none()) if not code_row: incr_counter("invite_code_failures") raise HTTPUnprocessableEntity("Invalid invite code") # create the user and set inviter to the owner of the invite code user = User(username, password) user.inviter_id = code_row.user_id # flush the user insert to db, will fail if username is already taken request.db_session.add(user) try: request.db_session.flush() except IntegrityError: raise HTTPUnprocessableEntity( "That username has already been registered.") # the flush above will generate the new user's ID, so use that to update the invite # code with info about the user that registered with it code_row.invitee_id = user.user_id # subscribe the new user to all groups except ~test all_groups = request.query(Group).all() for group in all_groups: if group.path == "test": continue request.db_session.add(GroupSubscription(user, group)) _send_welcome_message(user, request) incr_counter("registrations") # log the user in to the new account remember(request, user.user_id) # set request.user before logging so the user is associated with the event request.user = user request.db_session.add(Log(LogEventType.USER_REGISTER, request)) # redirect to the front page raise HTTPFound(location="/")
def post_login(request: Request, username: str, password: str, from_url: str) -> Response: """Process a log in request.""" incr_counter("logins") # Look up the user for the supplied username user = (request.query(User).undefer_all_columns().filter( User.username == username).one_or_none()) # If the username doesn't exist, tell them so - usually this isn't considered a good # practice, but it's completely trivial to check if a username exists on Tildes # anyway (by visiting /user/<username>), so it's better to just let people know if # they're trying to log in with the wrong username if not user: incr_counter("login_failures") # log the failure - need to manually commit because of the exception log_entry = Log( LogEventType.USER_LOG_IN_FAIL, request, { "username": username, "reason": "Nonexistent username" }, ) request.db_session.add(log_entry) request.tm.commit() raise HTTPUnprocessableEntity("That username does not exist") if not user.is_correct_password(password): incr_counter("login_failures") # log the failure - need to manually commit because of the exception log_entry = Log( LogEventType.USER_LOG_IN_FAIL, request, { "username": username, "reason": "Incorrect password" }, ) request.db_session.add(log_entry) request.tm.commit() raise HTTPUnprocessableEntity("Incorrect password") # Don't allow banned users to log in if user.is_banned: if user.ban_expiry_time: # add an hour to the ban's expiry time since the cronjob runs hourly unban_time = user.ban_expiry_time + timedelta(hours=1) unban_time = unban_time.strftime("%Y-%m-%d %H:%M (UTC)") raise HTTPUnprocessableEntity( "That account is temporarily banned. " f"The ban will be lifted at {unban_time}") raise HTTPUnprocessableEntity("That account has been banned") # If 2FA is enabled, save username to session and make user enter code if user.two_factor_enabled: request.session["two_factor_username"] = username return render_to_response( "tildes:templates/intercooler/login_two_factor.jinja2", { "keep": request.params.get("keep"), "from_url": from_url }, request=request, ) raise finish_login(request, user, from_url)