Esempio n. 1
0
def tfa_init_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(
        request.userid),
                                               request.params['password'],
                                               request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(
            define.webpage(
                request.userid,
                "control/2fa/init.html",
                [define.get_display_name(request.userid), "password"],
                title="Enable 2FA: Step 1"))
    # Unlikely that this block will get triggered, but just to be safe, check for it
    elif status == "unicode-failure":
        raise HTTPSeeOther(location='/signin/unicode-failure')
    # The user has authenticated, so continue with the initialization process.
    else:
        tfa_secret, tfa_qrcode = tfa.init(request.userid)
        _set_totp_code_on_session(tfa_secret)
        return Response(
            define.webpage(request.userid,
                           "control/2fa/init_qrcode.html", [
                               define.get_display_name(request.userid),
                               tfa_secret, tfa_qrcode, None
                           ],
                           title="Enable 2FA: Step 2"))
Esempio n. 2
0
def tfa_generate_recovery_codes_verify_password_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # 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 = request.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"))
Esempio n. 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"))
Esempio n. 4
0
def frienduser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if request.userid == otherid:
        return Response(
            define.errorpage(request.userid, "You cannot friend yourself."))

    if form.action == "sendfriendrequest":
        if not frienduser.check(request.userid,
                                otherid) and not frienduser.already_pending(
                                    request.userid, otherid):
            frienduser.request(request.userid, otherid)
    elif form.action == "withdrawfriendrequest":
        if frienduser.already_pending(request.userid, otherid):
            frienduser.remove_request(request.userid, otherid)
    elif form.action == "unfriend":
        frienduser.remove(request.userid, otherid)

    if form.feature == "pending":
        raise HTTPSeeOther(location="/manage/friends?feature=pending")
    else:  # typical value will be user
        raise HTTPSeeOther(
            location="/~%s" %
            (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 5
0
def help_verification_(request):
    username = define.get_display_name(request.userid)

    return Response(
        define.webpage(request.userid,
                       "help/verification.html", [username],
                       title="Account Verification"))
Esempio n. 6
0
def control_username_get_(request):
    latest_change = define.engine.execute(
        "SELECT username, active, extract(epoch from now() - replaced_at)::int8 AS seconds"
        " FROM username_history"
        " WHERE userid = %(user)s"
        " AND NOT cosmetic"
        " ORDER BY historyid DESC LIMIT 1",
        user=request.userid,
    ).first()

    if latest_change is None:
        existing_redirect = None
        days = None
    else:
        existing_redirect = latest_change.username if latest_change.active else None
        days = latest_change.seconds // (3600 * 24)

    return Response(
        define.webpage(
            request.userid,
            "control/username.html",
            (define.get_display_name(request.userid), existing_redirect,
             days if days is not None and days < 30 else None),
            title="Change Username",
        ))
Esempio n. 7
0
def tfa_init_qrcode_post_(request):
    # Strip any spaces from the TOTP code (some authenticators display the digits like '123 456')
    tfaresponse = request.params['tfaresponse'].replace(' ', '')
    tfa_secret_sess = _get_totp_code_from_session()

    # Check to see if the tfaresponse matches the tfasecret when run through the TOTP algorithm
    tfa_secret, recovery_codes = tfa.init_verify_tfa(tfa_secret_sess,
                                                     tfaresponse)

    # The 2FA TOTP code did not match with the generated 2FA secret
    if not tfa_secret:
        return Response(
            define.webpage(
                request.userid,
                "control/2fa/init_qrcode.html", [
                    define.get_display_name(request.userid), tfa_secret_sess,
                    tfa.generate_tfa_qrcode(request.userid, tfa_secret_sess),
                    "2fa"
                ],
                title="Enable 2FA: Step 2"))
    else:
        _set_recovery_codes_on_session(','.join(recovery_codes))
        return Response(
            define.webpage(request.userid,
                           "control/2fa/init_verify.html",
                           [recovery_codes, None],
                           title="Enable 2FA: Final Step"))
Esempio n. 8
0
def control_streaming_post_(request):
    form = request.web_input(target="",
                             set_stream="",
                             stream_length="",
                             stream_url="",
                             stream_text="")

    if form.target and request.userid not in staff.MODS:
        raise WeasylError('InsufficientPermissions')

    if form.target:
        target = int(form.target)
    else:
        target = request.userid

    stream_length = define.clamp(define.get_int(form.stream_length), 0, 360)
    p = orm.Profile()
    p.stream_text = form.stream_text
    p.stream_url = define.text_fix_url(form.stream_url.strip())
    set_stream = form.set_stream

    profile.edit_streaming_settings(request.userid,
                                    target,
                                    p,
                                    set_stream=set_stream,
                                    stream_length=stream_length)

    if form.target:
        target_username = define.get_sysname(define.get_display_name(target))
        raise HTTPSeeOther(location="/modcontrol/manageuser?name=" +
                           target_username)
    else:
        raise HTTPSeeOther(location="/control")
Esempio n. 9
0
def frienduser_(request):
    if not define.is_vouched_for(request.userid):
        raise WeasylError("vouchRequired")

    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if request.userid == otherid:
        raise WeasylError('cannotSelfFriend')

    if form.action == "sendfriendrequest":
        if not frienduser.check(request.userid,
                                otherid) and not frienduser.already_pending(
                                    request.userid, otherid):
            frienduser.request(request.userid, otherid)
    elif form.action == "withdrawfriendrequest":
        if frienduser.already_pending(request.userid, otherid):
            frienduser.remove_request(request.userid, otherid)
    elif form.action == "unfriend":
        frienduser.remove(request.userid, otherid)

    if form.feature == "pending":
        raise HTTPSeeOther(location="/manage/friends?feature=pending")
    else:  # typical value will be user
        raise HTTPSeeOther(
            location="/~%s" %
            (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 10
0
def vouch_(request):
    if not define.is_vouched_for(request.userid):
        raise WeasylError("vouchRequired")

    targetid = int(request.POST['targetid'])

    updated = define.engine.execute(
        "UPDATE login SET voucher = %(voucher)s WHERE userid = %(target)s AND voucher IS NULL RETURNING email",
        voucher=request.userid,
        target=targetid,
    ).first()

    target_username = define.get_display_name(targetid)

    if updated is not None:
        define._get_all_config.invalidate(targetid)
        emailer.send(updated.email, "Weasyl Account Verified",
                     define.render("email/verified.html", [target_username]))

    if target_username is None:
        assert updated is None
        raise WeasylError("Unexpected")

    raise HTTPSeeOther(location=request.route_path(
        'profile_tilde', name=define.get_sysname(target_username)))
Esempio n. 11
0
def tfa_generate_recovery_codes_verify_password_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # 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 = request.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"))
Esempio n. 12
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"))
Esempio n. 13
0
def control_streaming_post_(request):
    form = request.web_input(target="", set_stream="", stream_length="", stream_url="", stream_text="")

    if form.target and request.userid not in staff.MODS:
        return Response(define.errorpage(request.userid, errorcode.permission))

    if form.target:
        target = int(form.target)
    else:
        target = request.userid

    stream_length = define.clamp(define.get_int(form.stream_length), 0, 360)
    p = orm.Profile()
    p.stream_text = form.stream_text
    p.stream_url = define.text_fix_url(form.stream_url.strip())
    set_stream = form.set_stream

    profile.edit_streaming_settings(request.userid, target, p,
                                    set_stream=set_stream,
                                    stream_length=stream_length)

    if form.target:
        target_username = define.get_sysname(define.get_display_name(target))
        raise HTTPSeeOther(location="/modcontrol/manageuser?name=" + target_username)
    else:
        raise HTTPSeeOther(location="/control")
Esempio n. 14
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
Esempio n. 15
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"))
Esempio n. 16
0
def tfa_disable_post_(request):
    tfaresponse = request.params['tfaresponse']
    verify_checkbox = 'verify' in request.params

    if verify_checkbox:
        # If 2FA was successfully deactivated... return to 2FA dashboard
        if tfa.deactivate(request.userid, tfaresponse):
            raise HTTPSeeOther(location="/control/2fa/status")
        else:
            return Response(define.webpage(request.userid, "control/2fa/disable.html", [
                define.get_display_name(request.userid),
                "2fa"
            ], title="Disable 2FA"))
    # The user didn't check the verification checkbox (despite HTML5's client-side check)
    elif not verify_checkbox:
        return Response(define.webpage(request.userid, "control/2fa/disable.html", [
            define.get_display_name(request.userid),
            "verify"
        ], title="Disable 2FA"))
Esempio n. 17
0
def tfa_init_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(request.userid, "control/2fa/init.html", [
            define.get_display_name(request.userid),
            "password"
        ], title="Enable 2FA: Step 1"))
    # The user has authenticated, so continue with the initialization process.
    else:
        tfa_secret, tfa_qrcode = tfa.init(request.userid)
        _set_totp_code_on_session(tfa_secret)
        return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [
            define.get_display_name(request.userid),
            tfa_secret,
            tfa_qrcode,
            None
        ], title="Enable 2FA: Step 2"))
Esempio n. 18
0
def tfa_disable_post_(request):
    tfaresponse = request.params['tfaresponse']
    verify_checkbox = 'verify' in request.params

    if verify_checkbox:
        # If 2FA was successfully deactivated... return to 2FA dashboard
        if tfa.deactivate(request.userid, tfaresponse):
            raise HTTPSeeOther(location="/control/2fa/status")
        else:
            return Response(define.webpage(request.userid, "control/2fa/disable.html", [
                define.get_display_name(request.userid),
                "2fa"
            ], title="Disable 2FA"))
    # The user didn't check the verification checkbox (despite HTML5's client-side check)
    elif not verify_checkbox:
        return Response(define.webpage(request.userid, "control/2fa/disable.html", [
            define.get_display_name(request.userid),
            "verify"
        ], title="Disable 2FA"))
Esempio n. 19
0
def ignoreuser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if form.action == "ignore":
        ignoreuser.insert(request.userid, [otherid])
    elif form.action == "unignore":
        ignoreuser.remove(request.userid, [otherid])

    raise HTTPSeeOther(location="/~%s" %
                       (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 20
0
def select_myself(userid):
    if not userid:
        return None

    return {
        "userid": userid,
        "username": d.get_display_name(userid),
        "is_mod": userid in staff.MODS,
        "is_verified": d.is_vouched_for(userid),
        "user_media": media.get_user_media(userid),
    }
Esempio n. 21
0
def ignoreuser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if form.action == "ignore":
        if not ignoreuser.check(request.userid, otherid):
            ignoreuser.insert(request.userid, otherid)
    elif form.action == "unignore":
        ignoreuser.remove(request.userid, otherid)

    raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 22
0
def control_editprofile_put_(request):
    form = request.web_input(full_name="",
                             catchphrase="",
                             profile_text="",
                             set_commish="",
                             set_trade="",
                             set_request="",
                             set_stream="",
                             stream_url="",
                             stream_text="",
                             show_age="",
                             gender="",
                             country="",
                             profile_display="",
                             site_names=[],
                             site_values=[])

    if len(form.site_names) != len(form.site_values):
        raise WeasylError('Unexpected')

    if 'more' in form:
        form.username = define.get_display_name(request.userid)
        form.sorted_user_links = [
            (name, [value])
            for name, value in zip(form.site_names, form.site_values)
        ]
        form.settings = form.set_commish + form.set_trade + form.set_request
        form.config = form.profile_display
        return Response(
            define.webpage(request.userid,
                           "control/edit_profile.html", [form, form],
                           title="Edit Profile",
                           options=["typeahead"]))

    p = orm.Profile()
    p.full_name = form.full_name
    p.catchphrase = form.catchphrase
    p.profile_text = form.profile_text
    set_trade = profile.get_exchange_setting(profile.EXCHANGE_TYPE_TRADE,
                                             form.set_trade)
    set_request = profile.get_exchange_setting(profile.EXCHANGE_TYPE_REQUEST,
                                               form.set_request)
    set_commission = profile.get_exchange_setting(
        profile.EXCHANGE_TYPE_COMMISSION, form.set_commish)
    profile.edit_profile(request.userid,
                         p,
                         set_trade=set_trade,
                         set_request=set_request,
                         set_commission=set_commission,
                         profile_display=form.profile_display)

    profile.edit_userinfo(request.userid, form)

    raise HTTPSeeOther(location="/control")
Esempio n. 23
0
    def POST(self):
        form = web.input(userid="")
        otherid = define.get_int(form.userid)

        if form.action == "ignore":
            if not ignoreuser.check(self.user_id, otherid):
                ignoreuser.insert(self.user_id, otherid)
        elif form.action == "unignore":
            ignoreuser.remove(self.user_id, otherid)

        raise web.seeother("/~%s" % (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 24
0
def tfa_init_post_(request):
    userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid),
                                               request.params['password'], request=None)
    # The user's password failed to authenticate
    if status == "invalid":
        return Response(define.webpage(request.userid, "control/2fa/init.html", [
            define.get_display_name(request.userid),
            "password"
        ], title="Enable 2FA: Step 1"))
    # Unlikely that this block will get triggered, but just to be safe, check for it
    elif status == "unicode-failure":
        raise HTTPSeeOther(location='/signin/unicode-failure')
    # The user has authenticated, so continue with the initialization process.
    else:
        tfa_secret, tfa_qrcode = tfa.init(request.userid)
        _set_totp_code_on_session(tfa_secret)
        return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [
            define.get_display_name(request.userid),
            tfa_secret,
            tfa_qrcode,
            None
        ], title="Enable 2FA: Step 2"))
Esempio n. 25
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"))
Esempio n. 26
0
def followuser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if request.userid == otherid:
        raise WeasylError("cannotSelfFollow")

    if form.action == "follow":
        followuser.insert(request.userid, otherid)
    elif form.action == "unfollow":
        followuser.remove(request.userid, otherid)

    raise HTTPSeeOther(location="/~%s" %
                       (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 27
0
    def POST(self):
        form = web.input(userid="")
        otherid = define.get_int(form.userid)

        if self.user_id == otherid:
            return define.errorpage(self.user_id, "You cannot follow yourself.")

        if form.action == "follow":
            if not followuser.check(self.user_id, otherid):
                followuser.insert(self.user_id, otherid)
        elif form.action == "unfollow":
            followuser.remove(self.user_id, otherid)

        raise web.seeother("/~%s" % (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 28
0
def followuser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if request.userid == otherid:
        return Response(define.errorpage(request.userid, "You cannot follow yourself."))

    if form.action == "follow":
        if not followuser.check(request.userid, otherid):
            followuser.insert(request.userid, otherid)
    elif form.action == "unfollow":
        followuser.remove(request.userid, otherid)

    raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 29
0
def followuser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if request.userid == otherid:
        return Response(
            define.errorpage(request.userid, "You cannot follow yourself."))

    if form.action == "follow":
        if not followuser.check(request.userid, otherid):
            followuser.insert(request.userid, otherid)
    elif form.action == "unfollow":
        followuser.remove(request.userid, otherid)

    raise HTTPSeeOther(location="/~%s" %
                       (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 30
0
def test_init():
    """
    Verify we get a usable 2FA Secret and QRCode from init()
    """
    user_id = db_utils.create_user()
    tfa_secret, tfa_qrcode = tfa.init(user_id)

    computed_uri = pyotp.TOTP(tfa_secret).provisioning_uri(d.get_display_name(user_id), issuer_name="Weasyl")
    qr = QrCode.encode_text(computed_uri, QrCode.Ecc.MEDIUM)
    qr_xml = qr.to_svg_str(4)
    # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work
    qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n', '')).group(0)
    computed_qrcode = urllib.quote(qr_svg_only)
    # The QRcode we make locally should match that from init()
    assert tfa_qrcode == computed_qrcode
    # The tfa_secret from init() should be 16 characters, and work if passed in to pyotp.TOTP.now()
    assert len(tfa_secret) == 16
    assert len(pyotp.TOTP(tfa_secret).now()) == tfa.LENGTH_TOTP_CODE
Esempio n. 31
0
def test_init():
    """
    Verify we get a usable 2FA Secret and QRCode from init()
    """
    user_id = db_utils.create_user()
    tfa_secret, tfa_qrcode = tfa.init(user_id)

    computed_uri = pyotp.TOTP(tfa_secret).provisioning_uri(
        d.get_display_name(user_id), issuer_name="Weasyl")
    qr = QrCode.encode_text(computed_uri, QrCode.Ecc.MEDIUM)
    qr_xml = qr.to_svg_str(4)
    # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work
    qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n',
                                                             '')).group(0)
    computed_qrcode = urlquote(qr_svg_only)
    # The QRcode we make locally should match that from init()
    assert tfa_qrcode == computed_qrcode
    # The tfa_secret from init() should be 16 characters, and work if passed in to pyotp.TOTP.now()
    assert len(tfa_secret) == 16
    assert len(pyotp.TOTP(tfa_secret).now()) == tfa.LENGTH_TOTP_CODE
Esempio n. 32
0
def frienduser_(request):
    form = request.web_input(userid="")
    otherid = define.get_int(form.userid)

    if request.userid == otherid:
        return Response(define.errorpage(request.userid, "You cannot friend yourself."))

    if form.action == "sendfriendrequest":
        if not frienduser.check(request.userid, otherid) and not frienduser.already_pending(request.userid, otherid):
            frienduser.request(request.userid, otherid)
    elif form.action == "withdrawfriendrequest":
        if frienduser.already_pending(request.userid, otherid):
            frienduser.remove_request(request.userid, otherid)
    elif form.action == "unfriend":
        frienduser.remove(request.userid, otherid)

    if form.feature == "pending":
        raise HTTPSeeOther(location="/manage/friends?feature=pending")
    else:  # typical value will be user
        raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 33
0
    def POST(self):
        form = web.input(userid="")
        otherid = define.get_int(form.userid)

        if self.user_id == otherid:
            return define.errorpage(self.user_id, "You cannot friend yourself.")

        if form.action == "sendfriendrequest":
            if not frienduser.check(self.user_id, otherid) and not frienduser.already_pending(self.user_id, otherid):
                frienduser.request(self.user_id, otherid)
        elif form.action == "withdrawfriendrequest":
            if frienduser.already_pending(self.user_id, otherid):
                frienduser.remove_request(self.user_id, otherid)
        elif form.action == "unfriend":
            frienduser.remove(self.user_id, otherid)

        if form.feature == "pending":
            raise web.seeother("/manage/friends?feature=pending")
        else:  # typical value will be user
            raise web.seeother("/~%s" % (define.get_sysname(define.get_display_name(otherid))))
Esempio n. 34
0
def generate_tfa_qrcode(userid, tfa_secret):
    """
    Generate a 2FA QRCode on-demand, with the provisioning URI based on the supplied 2FA-secret.

    Used as a helper function for init(), or to regenerate the QRCode from a failed attempt
    at verifying the init()/init_verify_tfa() phases.

    Parameters:
        userid: The userid for the calling user; used to retrieve the username for the provisioning URI.
        tfa_secret: The tfa_secret as generated from init(), initially.

    Returns: A URL-quoted SVG--containing the TOTP provisioning URI--capable of being inserted into
             a data-uri for display to the user.
    """
    totp_uri = pyotp.TOTP(tfa_secret).provisioning_uri(d.get_display_name(userid), issuer_name="Weasyl")
    # Generate the QRcode
    qr = QrCode.encode_text(totp_uri, QrCode.Ecc.MEDIUM)
    qr_xml = qr.to_svg_str(4)
    # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work
    qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n', '')).group(0)
    return urllib.quote(qr_svg_only)
Esempio n. 35
0
def tfa_init_qrcode_post_(request):
    # Strip any spaces from the TOTP code (some authenticators display the digits like '123 456')
    tfaresponse = request.params['tfaresponse'].replace(' ', '')
    tfa_secret_sess = _get_totp_code_from_session()

    # Check to see if the tfaresponse matches the tfasecret when run through the TOTP algorithm
    tfa_secret, recovery_codes = tfa.init_verify_tfa(request.userid, tfa_secret_sess, tfaresponse)

    # The 2FA TOTP code did not match with the generated 2FA secret
    if not tfa_secret:
        return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [
            define.get_display_name(request.userid),
            tfa_secret_sess,
            tfa.generate_tfa_qrcode(request.userid, tfa_secret_sess),
            "2fa"
        ], title="Enable 2FA: Step 2"))
    else:
        _set_recovery_codes_on_session(','.join(recovery_codes))
        return Response(define.webpage(request.userid, "control/2fa/init_verify.html", [
            recovery_codes,
            None
        ], title="Enable 2FA: Final Step"))
Esempio n. 36
0
def vouch_(request):
    if not define.is_vouched_for(request.userid):
        raise WeasylError("vouchRequired")

    targetid = int(request.POST['targetid'])

    result = define.engine.execute(
        "UPDATE login SET voucher = %(voucher)s WHERE userid = %(target)s AND voucher IS NULL",
        voucher=request.userid,
        target=targetid,
    )

    if result.rowcount != 0:
        define._get_all_config.invalidate(targetid)

    target_username = define.get_display_name(targetid)

    if target_username is None:
        assert result.rowcount == 0
        raise WeasylError("Unexpected")

    raise HTTPSeeOther(location=request.route_path(
        'profile_tilde', name=define.get_sysname(target_username)))
Esempio n. 37
0
def generate_tfa_qrcode(userid, tfa_secret):
    """
    Generate a 2FA QRCode on-demand, with the provisioning URI based on the supplied 2FA-secret.

    Used as a helper function for init(), or to regenerate the QRCode from a failed attempt
    at verifying the init()/init_verify_tfa() phases.

    Parameters:
        userid: The userid for the calling user; used to retrieve the username for the provisioning URI.
        tfa_secret: The tfa_secret as generated from init(), initially.

    Returns: A URL-quoted SVG--containing the TOTP provisioning URI--capable of being inserted into
             a data-uri for display to the user.
    """
    totp_uri = pyotp.TOTP(tfa_secret).provisioning_uri(
        d.get_display_name(userid), issuer_name="Weasyl")
    # Generate the QRcode
    qr = QrCode.encode_text(totp_uri, QrCode.Ecc.MEDIUM)
    qr_xml = qr.to_svg_str(4)
    # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work
    qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n',
                                                             '')).group(0)
    return urllib.quote(qr_svg_only)
Esempio n. 38
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"))
Esempio n. 39
0
File: api.py Progetto: Weasyl/weasyl
def api_whoami_(request):
    return {
        "login": d.get_display_name(request.userid),
        "userid": request.userid,
    }
Esempio n. 40
0
File: api.py Progetto: 0x15/weasyl
 def GET(self):
     return json.dumps({
         "login": d.get_display_name(self.user_id),
         "userid": self.user_id,
     })
Esempio n. 41
0
def tfa_init_get_(request):
    return Response(define.webpage(request.userid, "control/2fa/init.html", [
        define.get_display_name(request.userid),
        None
    ], title="Enable 2FA: Step 1"))
Esempio n. 42
0
def change_username(acting_user, target_user, bypass_limit, new_username):
    new_username = clean_display_name(new_username)
    new_sysname = d.get_sysname(new_username)

    old_username = d.get_display_name(target_user)
    old_sysname = d.get_sysname(old_username)

    if new_username == old_username:
        return

    cosmetic = new_sysname == old_sysname

    def change_username_transaction(db):
        if not cosmetic and not bypass_limit:
            seconds = db.scalar(
                "SELECT extract(epoch from now() - replaced_at)::int8"
                " FROM username_history"
                " WHERE userid = %(target)s"
                " AND NOT cosmetic"
                " ORDER BY historyid DESC LIMIT 1",
                target=target_user,
            )

            if seconds is not None:
                days = seconds // (3600 * 24)

                if days < 30:
                    raise WeasylError("usernameChangedTooRecently")

        if not cosmetic:
            release_username(
                db,
                acting_user=acting_user,
                target_user=target_user,
            )

            conflict = db.scalar(
                "SELECT EXISTS (SELECT FROM login WHERE login_name = %(new_sysname)s AND userid != %(target)s)"
                " OR EXISTS (SELECT FROM useralias WHERE alias_name = %(new_sysname)s)"
                " OR EXISTS (SELECT FROM logincreate WHERE login_name = %(new_sysname)s)"
                " OR EXISTS (SELECT FROM username_history WHERE active AND login_name = %(new_sysname)s)",
                target=target_user,
                new_sysname=new_sysname,
            )

            if conflict:
                raise WeasylError("usernameExists")

        db.execute(
            "INSERT INTO username_history (userid, username, login_name, replaced_at, replaced_by, active, cosmetic)"
            " VALUES (%(target)s, %(old_username)s, %(old_sysname)s, now(), %(target)s, NOT %(cosmetic)s, %(cosmetic)s)",
            target=target_user,
            old_username=old_username,
            old_sysname=old_sysname,
            cosmetic=cosmetic,
        )

        if not cosmetic:
            result = db.execute(
                "UPDATE login SET login_name = %(new_sysname)s WHERE userid = %(target)s AND login_name = %(old_sysname)s",
                target=target_user,
                old_sysname=old_sysname,
                new_sysname=new_sysname,
            )

            if result.rowcount != 1:
                raise WeasylError("Unexpected")

        db.execute(
            "UPDATE profile SET username = %(new_username)s WHERE userid = %(target)s",
            target=target_user,
            new_username=new_username,
        )

    d.serializable_retry(change_username_transaction)
    d._get_display_name.invalidate(target_user)
Esempio n. 43
0
def tfa_disable_get_(request):
    return Response(
        define.webpage(request.userid,
                       "control/2fa/disable.html",
                       [define.get_display_name(request.userid), None],
                       title="Disable 2FA"))
Esempio n. 44
0
def tfa_init_get_(request):
    return Response(
        define.webpage(request.userid,
                       "control/2fa/init.html",
                       [define.get_display_name(request.userid), None],
                       title="Enable 2FA: Step 1"))
Esempio n. 45
0
def edit_email_password(userid, username, password, newemail, newemailcheck,
                        newpassword, newpasscheck):
    """
    Edit the email address and/or password for a given Weasyl account.

    After verifying the user's current login credentials, edit the user's email address and/or
    password if validity checks pass. If the email is modified, a confirmation email is sent
    to the user's target email with a token which, if used, finalizes the email address change.

    Parameters:
        userid: The `userid` of the Weasyl account to modify.
        username: User-entered username for password-based authentication.
        password: The user's current plaintext password.
        newemail: If changing the email on the account, the new email address. Optional.
        newemailcheck: A verification field for the above to serve as a typo-check. Optional,
        but mandatory if `newemail` provided.
        newpassword: If changing the password, the user's new password. Optional.
        newpasswordcheck: Verification field for `newpassword`. Optional, but mandatory if
        `newpassword` provided.
    """
    from weasyl import login

    # Track if any changes were made for later display back to the user.
    changes_made = ""

    # Check that credentials are correct
    logid, logerror = login.authenticate_bcrypt(username, password, request=None)

    # Run checks prior to modifying anything...
    if userid != logid or logerror is not None:
        raise WeasylError("loginInvalid")

    if newemail:
        if newemail != newemailcheck:
            raise WeasylError("emailMismatch")

    if newpassword:
        if newpassword != newpasscheck:
            raise WeasylError("passwordMismatch")
        elif not login.password_secure(newpassword):
            raise WeasylError("passwordInsecure")

    # If we are setting a new email, then write the email into a holding table pending confirmation
    #   that the email is valid.
    if newemail:
        # Only actually attempt to change the email if unused; prevent finding out if an email is already registered
        if not login.email_exists(newemail):
            token = security.generate_key(40)
            # Store the current token & email, updating them to overwrite a previous attempt if needed
            d.engine.execute("""
                INSERT INTO emailverify (userid, email, token, createtimestamp)
                VALUES (%(userid)s, %(newemail)s, %(token)s, NOW())
                ON CONFLICT (userid) DO
                  UPDATE SET email = %(newemail)s, token = %(token)s, createtimestamp = NOW()
            """, userid=userid, newemail=newemail, token=token)

            # Send out the email containing the verification token.
            emailer.append([newemail], None, "Weasyl Email Change Confirmation", d.render("email/verify_emailchange.html", [token, d.get_display_name(userid)]))
        else:
            # The target email exists: let the target know this
            query_username = d.engine.scalar("""
                SELECT login_name FROM login WHERE email = %(email)s
            """, email=newemail)
            emailer.append([newemail], None, "Weasyl Account Information - Duplicate Email on Accounts Rejected", d.render(
                "email/email_in_use_email_change.html", [query_username])
            )

        # Then add text to `changes_made` telling that we have completed the email change request, and how to proceed.
        changes_made += "Your email change request is currently pending. An email has been sent to **" + newemail + "**. Follow the instructions within to finalize your email address change.\n"

    # If the password is being updated, update the hash, and clear other sessions.
    if newpassword:
        d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(newpassword), userid])

        # Invalidate all sessions for `userid` except for the current one
        invalidate_other_sessions(userid)

        # Then add to `changes_made` detailing that the password change has successfully occurred.
        changes_made += "Your password has been successfully changed. As a security precaution, you have been logged out of all other active sessions."

    if changes_made != "":
        return changes_made
    else:
        return False
Esempio n. 46
0
def edit_email_password(userid, username, password, newemail, newemailcheck,
                        newpassword, newpasscheck):
    """
    Edit the email address and/or password for a given Weasyl account.

    After verifying the user's current login credentials, edit the user's email address and/or
    password if validity checks pass. If the email is modified, a confirmation email is sent
    to the user's target email with a token which, if used, finalizes the email address change.

    Parameters:
        userid: The `userid` of the Weasyl account to modify.
        username: User-entered username for password-based authentication.
        password: The user's current plaintext password.
        newemail: If changing the email on the account, the new email address. Optional.
        newemailcheck: A verification field for the above to serve as a typo-check. Optional,
        but mandatory if `newemail` provided.
        newpassword: If changing the password, the user's new password. Optional.
        newpasswordcheck: Verification field for `newpassword`. Optional, but mandatory if
        `newpassword` provided.
    """
    from weasyl import login

    # Track if any changes were made for later display back to the user.
    changes_made = ""

    # Check that credentials are correct
    logid, logerror = login.authenticate_bcrypt(username,
                                                password,
                                                request=None)

    # Run checks prior to modifying anything...
    if userid != logid or logerror is not None:
        raise WeasylError("loginInvalid")

    if newemail:
        if newemail != newemailcheck:
            raise WeasylError("emailMismatch")

    if newpassword:
        if newpassword != newpasscheck:
            raise WeasylError("passwordMismatch")
        elif not login.password_secure(newpassword):
            raise WeasylError("passwordInsecure")

    # If we are setting a new email, then write the email into a holding table pending confirmation
    #   that the email is valid.
    if newemail:
        # Only actually attempt to change the email if unused; prevent finding out if an email is already registered
        if not login.email_exists(newemail):
            token = security.generate_key(40)
            # Store the current token & email, updating them to overwrite a previous attempt if needed
            d.engine.execute("""
                INSERT INTO emailverify (userid, email, token, createtimestamp)
                VALUES (%(userid)s, %(newemail)s, %(token)s, NOW())
                ON CONFLICT (userid) DO
                  UPDATE SET email = %(newemail)s, token = %(token)s, createtimestamp = NOW()
            """,
                             userid=userid,
                             newemail=newemail,
                             token=token)

            # Send out the email containing the verification token.
            emailer.append([newemail], None,
                           "Weasyl Email Change Confirmation",
                           d.render("email/verify_emailchange.html",
                                    [token, d.get_display_name(userid)]))
        else:
            # The target email exists: let the target know this
            query_username = d.engine.scalar("""
                SELECT login_name FROM login WHERE email = %(email)s
            """,
                                             email=newemail)
            emailer.append(
                [newemail], None,
                "Weasyl Account Information - Duplicate Email on Accounts Rejected",
                d.render("email/email_in_use_email_change.html",
                         [query_username]))

        # Then add text to `changes_made` telling that we have completed the email change request, and how to proceed.
        changes_made += "Your email change request is currently pending. An email has been sent to **" + newemail + "**. Follow the instructions within to finalize your email address change.\n"

    # If the password is being updated, update the hash, and clear other sessions.
    if newpassword:
        d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i",
                  [login.passhash(newpassword), userid])

        # Invalidate all sessions for `userid` except for the current one
        invalidate_other_sessions(userid)

        # Then add to `changes_made` detailing that the password change has successfully occurred.
        changes_made += "Your password has been successfully changed. As a security precaution, you have been logged out of all other active sessions."

    if changes_made != "":
        return changes_made
    else:
        return False
Esempio n. 47
0
def tfa_disable_get_(request):
    return Response(define.webpage(request.userid, "control/2fa/disable.html", [
        define.get_display_name(request.userid),
        None
    ], title="Disable 2FA"))
Esempio n. 48
0
def select_comments(userid):
    queries = []

    # Shout comments
    current_username = d.get_sysname(d.get_display_name(userid))
    queries.append({
        "type": 4010,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "ownername": current_username,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.targetid, pr.username
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
        WHERE we.userid = %(user)s
            AND we.type = 4010
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Shout replies
    queries.append({
        "type": 4015,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "ownerid": i.ownerid,
        "ownername": i.owner_username,
        "replyid": i.referid,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT
            we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, px.userid AS ownerid,
            px.username AS owner_username
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN comments sh ON we.referid = sh.commentid
            INNER JOIN profile px ON sh.target_user = px.userid
        WHERE we.userid = %(user)s
            AND we.type = 4015
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Staff comment replies
    queries.append({
        "type": 4016,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "ownerid": i.ownerid,
        "ownername": i.owner_username,
        "replyid": i.referid,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT
            we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, px.userid AS ownerid,
            px.username AS owner_username
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN comments sh ON we.referid = sh.commentid
            INNER JOIN profile px ON sh.target_user = px.userid
        WHERE we.userid = %(user)s
            AND we.type = 4016
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Submission comments
    queries.append({
        "type": 4020,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "submitid": i.referid,
        "title": i.title,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, su.title
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN submission su ON we.referid = su.submitid
        WHERE we.userid = %(user)s
            AND we.type = 4020
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Submission comment replies
    queries.append({
        "type": 4025,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "submitid": i.submitid,
        "title": i.title,
        "replyid": i.referid,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, su.submitid, su.title
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN comments sc ON we.referid = sc.commentid
            INNER JOIN submission su ON sc.target_sub = su.submitid
        WHERE we.userid = %(user)s
            AND we.type = 4025
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Character comments
    queries.append({
        "type": 4040,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "charid": i.referid,
        "title": i.char_name,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, ch.char_name
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN character ch ON we.referid = ch.charid
        WHERE we.userid = %(user)s
            AND we.type = 4040
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Character comment replies
    queries.append({
        "type": 4045,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "charid": i.charid,
        "title": i.char_name,
        "replyid": i.referid,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, ch.charid, ch.char_name
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN charcomment cc ON we.referid = cc.commentid
            INNER JOIN character ch ON cc.targetid = ch.charid
        WHERE we.userid = %(user)s
            AND we.type = 4045
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Journal comments
    queries.append({
        "type": 4030,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "journalid": i.referid,
        "title": i.title,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, jo.title
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN journal jo ON we.referid = jo.journalid
        WHERE we.userid = %(user)s
            AND we.type = 4030
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Journal comment replies
    queries.append({
        "type": 4035,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "journalid": i.journalid,
        "title": i.title,
        "replyid": i.referid,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, jo.journalid, jo.title
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN journalcomment jc ON we.referid = jc.commentid
            INNER JOIN journal jo ON jc.targetid = jo.journalid
        WHERE we.userid = %(user)s
            AND we.type = 4035
        ORDER BY we.unixtime DESC
    """, user=userid))

    # Collection comments
    queries.append({
        "type": 4050,
        "id": i.welcomeid,
        "unixtime": i.unixtime,
        "userid": i.otherid,
        "username": i.username,
        "submitid": i.referid,
        "title": i.title,
        "commentid": i.targetid,
    } for i in d.engine.execute("""
        SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, su.title
        FROM welcome we
            INNER JOIN profile pr ON we.otherid = pr.userid
            INNER JOIN submission su ON we.referid = su.submitid
        WHERE we.userid = %(user)s
            AND we.type = 4050
        ORDER BY we.unixtime DESC
    """, user=userid))

    return list(chain(*queries))
Esempio n. 49
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))