Exemple #1
0
def tfa_generate_recovery_codes_verify_password_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], session=False)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(
            request.userid,
            "control/2fa/generate_recovery_codes_verify_password.html",
            ["password"],
            title="Generate Recovery Codes: Verify Password"
        ))
    # The user has authenticated, so continue with generating the new recovery codes.
    else:
        # Edge case prevention: Stop the user from having two Weasyl sessions open and trying
        #   to proceed through the generation process with two sets of recovery codes.
        invalidate_other_sessions(request.userid)
        # Edge case prevention: Do we have existing (and recent) codes on this session? Prevent
        #   a user from confusing themselves if they visit the request page twice.
        sess = define.get_weasyl_session()
        gen_rec_codes = True
        if '2fa_recovery_codes_timestamp' in sess.additional_data:
            # Are the codes on the current session < 30 minutes old?
            tstamp = sess.additional_data['2fa_recovery_codes_timestamp']
            if arrow.now().timestamp - tstamp < 1800:
                # We have recent codes on the session, use them instead of generating fresh codes.
                recovery_codes = sess.additional_data['2fa_recovery_codes'].split(',')
                gen_rec_codes = False
        if gen_rec_codes:
            # Either this is a fresh request to generate codes, or the timelimit was exceeded.
            recovery_codes = tfa.generate_recovery_codes()
            _set_recovery_codes_on_session(','.join(recovery_codes))
        return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [
            recovery_codes,
            None
        ], title="Generate Recovery Codes: Save New Recovery Codes"))
Exemple #2
0
def signin_2fa_auth_get_(request):
    sess = define.get_weasyl_session()

    # Only render page if the password has been authenticated (we have a UserID stored in the session)
    if '2fa_pwd_auth_userid' not in sess.additional_data:
        return Response(define.errorpage(request.userid, errorcode.permission))
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    # Maximum secondary authentication time: 5 minutes
    session_life = arrow.now(
    ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        _cleanup_2fa_session()
        return Response(
            define.errorpage(
                request.userid, errorcode.
                error_messages['TwoFactorAuthenticationAuthenticationTimeout'],
                [["Sign In", "/signin"], ["Return to the Home Page", "/"]]))
    else:
        ref = request.params["referer"] if "referer" in request.params else "/"
        return Response(
            define.webpage(
                request.userid,
                "etc/signin_2fa_auth.html", [
                    define.get_display_name(tfa_userid), ref,
                    two_factor_auth.get_number_of_recovery_codes(tfa_userid),
                    None
                ],
                title="Sign In - 2FA"))
Exemple #3
0
def signin_2fa_auth_get_(request):
    sess = define.get_weasyl_session()

    # Only render page if the session exists //and// the password has
    # been authenticated (we have a UserID stored in the session)
    if not sess.additional_data or '2fa_pwd_auth_userid' not in sess.additional_data:
        raise WeasylError('InsufficientPermissions')
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    # Maximum secondary authentication time: 5 minutes
    session_life = arrow.now(
    ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        _cleanup_2fa_session()
        raise WeasylError('TwoFactorAuthenticationAuthenticationTimeout')
    else:
        ref = request.params["referer"] if "referer" in request.params else "/"
        return Response(
            define.webpage(
                request.userid,
                "etc/signin_2fa_auth.html", [
                    define.get_display_name(tfa_userid), ref,
                    two_factor_auth.get_number_of_recovery_codes(tfa_userid),
                    None
                ],
                title="Sign In - 2FA"))
Exemple #4
0
def signin(userid):
    # Update the last login record for the user
    d.execute("UPDATE login SET last_login = %i WHERE userid = %i", [d.get_time(), userid])

    # set the userid on the session
    sess = d.get_weasyl_session()
    sess.userid = userid
    sess.save = True
Exemple #5
0
def signin_post_(request):
    form = request.web_input(username="", password="", referer="", sfwmode="nsfw")
    form.referer = form.referer or '/'

    logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent)

    if logid and logerror is None:
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings.
        index.template_fields.invalidate(logid)
        raise HTTPSeeOther(location=form.referer)
    elif logid and logerror == "2fa":
        # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now)
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        index.template_fields.invalidate(logid)
        # Check if out of recovery codes; this should *never* execute normally, save for crafted
        #   webtests. However, check for it and log an error to Sentry if it happens.
        remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid)
        if remaining_recovery_codes == 0:
            raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " +
                               str(logid) + " was zero upon password authentication succeeding, " +
                               "which should be impossible.")
        # Store the authenticated userid & password auth time to the session
        sess = define.get_weasyl_session()
        # The timestamp at which password authentication succeeded
        sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp
        # The userid of the user attempting authentication
        sess.additional_data['2fa_pwd_auth_userid'] = logid
        # The number of times the user has attempted to authenticate via 2FA
        sess.additional_data['2fa_pwd_auth_attempts'] = 0
        sess.save = True
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(logid), form.referer, remaining_recovery_codes, None],
            title="Sign In - 2FA"
        ))
    elif logerror == "invalid":
        return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer]))
    elif logerror == "banned":
        reason = moderation.get_ban_reason(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been permanently banned and you are no longer allowed "
            "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
            "contact %s for assistance." % (reason, MACRO_SUPPORT_ADDRESS)))
    elif logerror == "suspended":
        suspension = moderation.get_suspension(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been temporarily suspended and you are not allowed to "
            "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
            "%s.\n\nIf you believe this suspension is in error, please contact "
            "%s for assistance." % (suspension.reason, define.convert_date(suspension.release), MACRO_SUPPORT_ADDRESS)))

    raise WeasylError("Unexpected")  # pragma: no cover
Exemple #6
0
def _cleanup_session():
    sess = define.get_weasyl_session()
    if '2fa_recovery_codes' in sess.additional_data:
        del sess.additional_data['2fa_recovery_codes']
    if '2fa_recovery_codes_timestamp' in sess.additional_data:
        del sess.additional_data['2fa_recovery_codes_timestamp']
    if '2fa_totp_code' in sess.additional_data:
        del sess.additional_data['2fa_totp_code']
    sess.save = True
Exemple #7
0
def signin(userid):
    # Update the last login record for the user
    d.execute("UPDATE login SET last_login = %i WHERE userid = %i",
              [d.get_time(), userid])

    # set the userid on the session
    sess = d.get_weasyl_session()
    sess.userid = userid
    sess.save = True
Exemple #8
0
def _cleanup_session():
    sess = define.get_weasyl_session()
    if '2fa_recovery_codes' in sess.additional_data:
        del sess.additional_data['2fa_recovery_codes']
    if '2fa_recovery_codes_timestamp' in sess.additional_data:
        del sess.additional_data['2fa_recovery_codes_timestamp']
    if '2fa_totp_code' in sess.additional_data:
        del sess.additional_data['2fa_totp_code']
    sess.save = True
Exemple #9
0
def signin_2fa_auth_post_(request):
    sess = define.get_weasyl_session()

    # Only render page if the password has been authenticated (we have a UserID stored in the session)
    if '2fa_pwd_auth_userid' not in sess.additional_data:
        return Response(define.errorpage(request.userid, errorcode.permission))
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    session_life = arrow.now(
    ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        # Maximum secondary authentication time: 5 minutes
        _cleanup_2fa_session()
        return Response(
            define.errorpage(
                request.userid, errorcode.
                error_messages['TwoFactorAuthenticationAuthenticationTimeout'],
                [["Sign In", "/signin"], ["Return to the Home Page", "/"]]))
    elif two_factor_auth.verify(tfa_userid, request.params["tfaresponse"]):
        # 2FA passed, so login and cleanup.
        _cleanup_2fa_session()
        login.signin(tfa_userid)
        ref = request.params["referer"] or "/"
        # User is out of recovery codes, so force-deactivate 2FA
        if two_factor_auth.get_number_of_recovery_codes(tfa_userid) == 0:
            two_factor_auth.force_deactivate(tfa_userid)
            return Response(
                define.errorpage(
                    tfa_userid, errorcode.error_messages[
                        'TwoFactorAuthenticationZeroRecoveryCodesRemaining'],
                    [["2FA Dashboard", "/control/2fa/status"],
                     ["Return to the Home Page", "/"]]))
        # Return to the target page, restricting to the path portion of 'ref' per urlparse.
        raise HTTPSeeOther(location=urlparse.urlparse(ref).path)
    elif sess.additional_data['2fa_pwd_auth_attempts'] >= 5:
        # Hinder brute-forcing the 2FA token or recovery code by enforcing an upper-bound on 2FA auth attempts.
        _cleanup_2fa_session()
        return Response(
            define.errorpage(
                request.userid, errorcode.error_messages[
                    'TwoFactorAuthenticationAuthenticationAttemptsExceeded'],
                [["Sign In", "/signin"], ["Return to the Home Page", "/"]]))
    else:
        # Log the failed authentication attempt to the session and save
        sess.additional_data['2fa_pwd_auth_attempts'] += 1
        sess.save = True
        # 2FA failed; redirect to 2FA input page & inform user that authentication failed.
        return Response(
            define.webpage(
                request.userid,
                "etc/signin_2fa_auth.html", [
                    define.get_display_name(tfa_userid),
                    request.params["referer"],
                    two_factor_auth.get_number_of_recovery_codes(tfa_userid),
                    "2fa"
                ],
                title="Sign In - 2FA"))
Exemple #10
0
def signin(userid):
    # Update the last login record for the user
    d.execute("UPDATE login SET last_login = %i WHERE userid = %i", [d.get_time(), userid])

    # Log the successful login and increment the login count
    d.append_to_log('login.success', userid=userid, ip=d.get_address())
    d.metric('increment', 'logins')

    # set the userid on the session
    sess = d.get_weasyl_session()
    sess.userid = userid
    sess.save = True
Exemple #11
0
def signin(userid):
    # Update the last login record for the user
    d.execute("UPDATE login SET last_login = %i WHERE userid = %i", [d.get_time(), userid])

    # Log the successful login and increment the login count
    d.append_to_log('login.success', userid=userid, ip=d.get_address())
    d.metric('increment', 'logins')

    # set the userid on the session
    sess = d.get_weasyl_session()
    sess.userid = userid
    sess.save = True
Exemple #12
0
def _cleanup_2fa_session():
    """
    Cleans up a Weasyl session of any 2FA data stored during the authentication process.

    Parameters: None; keys off of the currently active session making the request.

    Returns: Nothing.
    """
    sess = define.get_weasyl_session()
    del sess.additional_data['2fa_pwd_auth_timestamp']
    del sess.additional_data['2fa_pwd_auth_userid']
    del sess.additional_data['2fa_pwd_auth_attempts']
    sess.save = True
Exemple #13
0
def _cleanup_2fa_session():
    """
    Cleans up a Weasyl session of any 2FA data stored during the authentication process.

    Parameters: None; keys off of the currently active session making the request.

    Returns: Nothing.
    """
    sess = define.get_weasyl_session()
    del sess.additional_data['2fa_pwd_auth_timestamp']
    del sess.additional_data['2fa_pwd_auth_userid']
    del sess.additional_data['2fa_pwd_auth_attempts']
    sess.save = True
Exemple #14
0
def signin_2fa_auth_post_(request):
    sess = define.get_weasyl_session()

    # Only render page if the session exists //and// the password has
    # been authenticated (we have a UserID stored in the session)
    if not sess.additional_data or '2fa_pwd_auth_userid' not in sess.additional_data:
        return Response(define.errorpage(request.userid, errorcode.permission))
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    session_life = arrow.now().timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        # Maximum secondary authentication time: 5 minutes
        _cleanup_2fa_session()
        return Response(define.errorpage(
            request.userid,
            errorcode.error_messages['TwoFactorAuthenticationAuthenticationTimeout'],
            [["Sign In", "/signin"], ["Return to the Home Page", "/"]]
        ))
    elif two_factor_auth.verify(tfa_userid, request.params["tfaresponse"]):
        # 2FA passed, so login and cleanup.
        _cleanup_2fa_session()
        login.signin(request, tfa_userid, ip_address=request.client_addr, user_agent=request.user_agent)
        ref = request.params["referer"] or "/"
        # User is out of recovery codes, so force-deactivate 2FA
        if two_factor_auth.get_number_of_recovery_codes(tfa_userid) == 0:
            two_factor_auth.force_deactivate(tfa_userid)
            return Response(define.errorpage(
                tfa_userid,
                errorcode.error_messages['TwoFactorAuthenticationZeroRecoveryCodesRemaining'],
                [["2FA Dashboard", "/control/2fa/status"], ["Return to the Home Page", "/"]]
            ))
        # Return to the target page, restricting to the path portion of 'ref' per urlparse.
        raise HTTPSeeOther(location=urlparse.urlparse(ref).path)
    elif sess.additional_data['2fa_pwd_auth_attempts'] >= 5:
        # Hinder brute-forcing the 2FA token or recovery code by enforcing an upper-bound on 2FA auth attempts.
        _cleanup_2fa_session()
        return Response(define.errorpage(
            request.userid,
            errorcode.error_messages['TwoFactorAuthenticationAuthenticationAttemptsExceeded'],
            [["Sign In", "/signin"], ["Return to the Home Page", "/"]]
        ))
    else:
        # Log the failed authentication attempt to the session and save
        sess.additional_data['2fa_pwd_auth_attempts'] += 1
        sess.save = True
        # 2FA failed; redirect to 2FA input page & inform user that authentication failed.
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(tfa_userid), request.params["referer"], two_factor_auth.get_number_of_recovery_codes(tfa_userid),
             "2fa"], title="Sign In - 2FA"))
Exemple #15
0
def invalidate_other_sessions(userid):
    """
    Invalidate all HTTP sessions for `userid` except for the current session.

    Useful as a security precaution, such as if a user changes their password, or enables
    2FA.

    Parameters:
        userid: The userid for the account to clear sessions from.

    Returns: Nothing.
    """
    sess = d.get_weasyl_session()
    d.engine.execute("""
        DELETE FROM sessions
        WHERE userid = %(userid)s
          AND sessionid != %(currentsession)s
    """, userid=userid, currentsession=sess.sessionid)
Exemple #16
0
def invalidate_other_sessions(userid):
    """
    Invalidate all HTTP sessions for `userid` except for the current session.

    Useful as a security precaution, such as if a user changes their password, or enables
    2FA.

    Parameters:
        userid: The userid for the account to clear sessions from.

    Returns: Nothing.
    """
    sess = d.get_weasyl_session()
    d.engine.execute("""
        DELETE FROM sessions
        WHERE userid = %(userid)s
          AND sessionid != %(currentsession)s
    """, userid=userid, currentsession=sess.sessionid)
Exemple #17
0
def tfa_generate_recovery_codes_verify_password_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(
        request.userid),
                                               request.params['password'],
                                               session=False)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(
            define.webpage(
                request.userid,
                "control/2fa/generate_recovery_codes_verify_password.html",
                ["password"],
                title="Generate Recovery Codes: Verify Password"))
    # The user has authenticated, so continue with generating the new recovery codes.
    else:
        # Edge case prevention: Stop the user from having two Weasyl sessions open and trying
        #   to proceed through the generation process with two sets of recovery codes.
        invalidate_other_sessions(request.userid)
        # Edge case prevention: Do we have existing (and recent) codes on this session? Prevent
        #   a user from confusing themselves if they visit the request page twice.
        sess = define.get_weasyl_session()
        gen_rec_codes = True
        if '2fa_recovery_codes_timestamp' in sess.additional_data:
            # Are the codes on the current session < 30 minutes old?
            tstamp = sess.additional_data['2fa_recovery_codes_timestamp']
            if arrow.now().timestamp - tstamp < 1800:
                # We have recent codes on the session, use them instead of generating fresh codes.
                recovery_codes = sess.additional_data[
                    '2fa_recovery_codes'].split(',')
                gen_rec_codes = False
        if gen_rec_codes:
            # Either this is a fresh request to generate codes, or the timelimit was exceeded.
            recovery_codes = tfa.generate_recovery_codes()
            _set_recovery_codes_on_session(','.join(recovery_codes))
        return Response(
            define.webpage(
                request.userid,
                "control/2fa/generate_recovery_codes.html",
                [recovery_codes, None],
                title="Generate Recovery Codes: Save New Recovery Codes"))
Exemple #18
0
def signin_2fa_auth_get_(request):
    sess = define.get_weasyl_session()

    # Only render page if the password has been authenticated (we have a UserID stored in the session)
    if '2fa_pwd_auth_userid' not in sess.additional_data:
        return Response(define.errorpage(request.userid, errorcode.permission))
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    # Maximum secondary authentication time: 5 minutes
    session_life = arrow.now().timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        _cleanup_2fa_session()
        return Response(define.errorpage(
            request.userid,
            errorcode.error_messages['TwoFactorAuthenticationAuthenticationTimeout'],
            [["Sign In", "/signin"], ["Return to the Home Page", "/"]]))
    else:
        ref = request.params["referer"] if "referer" in request.params else "/"
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(tfa_userid), ref, two_factor_auth.get_number_of_recovery_codes(tfa_userid),
             None], title="Sign In - 2FA"))
Exemple #19
0
def _get_recovery_codes_from_session():
    sess = define.get_weasyl_session()
    if '2fa_recovery_codes' in sess.additional_data:
        return sess.additional_data['2fa_recovery_codes']
    else:
        return None
Exemple #20
0
def _set_recovery_codes_on_session(recovery_codes):
    sess = define.get_weasyl_session()
    sess.additional_data['2fa_recovery_codes'] = recovery_codes
    sess.additional_data['2fa_recovery_codes_timestamp'] = arrow.now(
    ).timestamp
    sess.save = True
Exemple #21
0
def _get_totp_code_from_session():
    sess = define.get_weasyl_session()
    return sess.additional_data['2fa_totp_code']
Exemple #22
0
def _set_totp_code_on_session(totp_code):
    sess = define.get_weasyl_session()
    sess.additional_data['2fa_totp_code'] = totp_code
    sess.save = True
Exemple #23
0
def signin_post_(request):
    form = request.web_input(username="", password="", referer="", sfwmode="nsfw")
    form.referer = form.referer or '/'

    logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent)

    if logid and logerror == 'unicode-failure':
        raise HTTPSeeOther(location='/signin/unicode-failure')
    elif logid and logerror is None:
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings.
        index.template_fields.invalidate(logid)
        raise HTTPSeeOther(location=form.referer)
    elif logid and logerror == "2fa":
        # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now)
        if form.sfwmode == "sfw":
            request.set_cookie_on_response("sfwmode", "sfw", 31536000)
        index.template_fields.invalidate(logid)
        # Check if out of recovery codes; this should *never* execute normally, save for crafted
        #   webtests. However, check for it and log an error to Sentry if it happens.
        remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid)
        if remaining_recovery_codes == 0:
            raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " +
                               str(logid) + " was zero upon password authentication succeeding, " +
                               "which should be impossible.")
        # Store the authenticated userid & password auth time to the session
        sess = define.get_weasyl_session()
        # The timestamp at which password authentication succeeded
        sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp
        # The userid of the user attempting authentication
        sess.additional_data['2fa_pwd_auth_userid'] = logid
        # The number of times the user has attempted to authenticate via 2FA
        sess.additional_data['2fa_pwd_auth_attempts'] = 0
        sess.save = True
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(logid), form.referer, remaining_recovery_codes, None],
            title="Sign In - 2FA"
        ))
    elif logerror == "invalid":
        return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer]))
    elif logerror == "banned":
        reason = moderation.get_ban_reason(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been permanently banned and you are no longer allowed "
            "to sign in.\n\n%s\n\nIf you believe this ban is in error, please "
            "contact [email protected] for assistance." % (reason,)))
    elif logerror == "suspended":
        suspension = moderation.get_suspension(logid)
        return Response(define.errorpage(
            request.userid,
            "Your account has been temporarily suspended and you are not allowed to "
            "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on "
            "%s.\n\nIf you believe this suspension is in error, please contact "
            "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release))))
    elif logerror == "address":
        return Response("IP ADDRESS TEMPORARILY BLOCKED")

    return Response(define.errorpage(request.userid))
Exemple #24
0
def _get_totp_code_from_session():
    sess = define.get_weasyl_session()
    return sess.additional_data['2fa_totp_code']
Exemple #25
0
def _set_totp_code_on_session(totp_code):
    sess = define.get_weasyl_session()
    sess.additional_data['2fa_totp_code'] = totp_code
    sess.save = True
Exemple #26
0
def _set_recovery_codes_on_session(recovery_codes):
    sess = define.get_weasyl_session()
    sess.additional_data['2fa_recovery_codes'] = recovery_codes
    sess.additional_data['2fa_recovery_codes_timestamp'] = arrow.now().timestamp
    sess.save = True
Exemple #27
0
def _get_recovery_codes_from_session():
    sess = define.get_weasyl_session()
    if '2fa_recovery_codes' in sess.additional_data:
        return sess.additional_data['2fa_recovery_codes']
    else:
        return None