Ejemplo n.º 1
0
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="/")
Ejemplo n.º 2
0
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='/')
Ejemplo n.º 3
0
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="/")
Ejemplo n.º 4
0
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")
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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="/")
Ejemplo n.º 7
0
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="/")
Ejemplo n.º 8
0
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)