Exemplo n.º 1
0
def test_password_secure():
    # Too short
    assert not login.password_secure(u"")
    assert not login.password_secure(u"Abc123*")
    assert not login.password_secure(u"Passw0rd")

    # Acceptable
    assert login.password_secure(u"abcdefghijkl")
Exemplo n.º 2
0
    def testPasswordLength(self):
        # Too short
        self.assertFalse(login.password_secure(u""))
        self.assertFalse(login.password_secure(u"Abc123*"))
        self.assertFalse(login.password_secure(u"Passw0rd"))

        # Acceptable
        self.assertTrue(login.password_secure(u"abcdefghijkl"))
Exemplo n.º 3
0
def edit_email_password(userid, username, password, newemail, newemailcheck,
                        newpassword, newpasscheck):
    from weasyl import login

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

    if userid != logid or logerror is not None:
        raise WeasylError("loginInvalid")

    if newemail:
        if newemail != newemailcheck:
            raise WeasylError("emailMismatch")
        elif login.email_exists(newemail):
            raise WeasylError("emailExists")

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

    if newemail:
        d.execute("UPDATE login SET email = '%s' WHERE userid = %i", [newemail, userid])

    if newpassword:
        d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(newpassword), userid])
Exemplo n.º 4
0
def force(userid, form):
    from weasyl import login

    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    d.execute("UPDATE login SET settings = REPLACE(settings, 'p', '') WHERE userid = %i", [userid])
    d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(form.password), userid])
    d.get_login_settings.invalidate(userid)
Exemplo n.º 5
0
def reset(token, password, passcheck, expect_userid, address):
    from weasyl import login

    token_sha256 = _hash_token(token)

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

    with d.engine.begin() as db:
        email = db.scalar(
            "DELETE FROM forgotpassword"
            " WHERE token_sha256 = %(token_sha256)s AND created_at >= now() - INTERVAL '1 hour'"
            " RETURNING email",
            token_sha256=bytearray(token_sha256),
        )

        if email is None:
            # token expired, or never existed.
            raise WeasylError("forgotpasswordRecordMissing")

        match = _find_reset_target(db, email=email)

        if match is None:
            # account changed e-mail addresses.
            raise WeasylError("forgotpasswordRecordMissing")

        if match.userid != expect_userid:
            # e-mail address changed accounts. possible, but very unusual.
            #
            # this is to make sure to never change the password of an account
            # different from the one promised on the form, not a security thing
            # – `expect_userid` is a hidden field of the form.
            raise WeasylError("forgotpasswordRecordMissing")

        # Update the authbcrypt table with a new password hash
        result = db.execute(
            "UPDATE authbcrypt SET hashsum = %(hash)s WHERE userid = %(user)s",
            user=match.userid,
            hash=login.passhash(password),
        )

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

        db.execute(d.meta.tables["user_events"].insert({
            "userid": match.userid,
            "event": "password-reset",
            "data": {
                "address": address,
            }
        }))
Exemplo n.º 6
0
def force(userid, form):
    from weasyl import login

    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    d.execute(
        "UPDATE login SET settings = REPLACE(settings, 'p', '') WHERE userid = %i",
        [userid])
    d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i",
              [login.passhash(form.password), userid])
    d.get_login_settings.invalidate(userid)
Exemplo n.º 7
0
def reset(form):
    from weasyl import login

    # Raise an exception if `password` does not enter `passcheck` (indicating
    # that the user mistyped one of the fields) or if `password` does not meet
    # the system's password security requirements
    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    # Select the user information and record data from the forgotpassword table
    # pertaining to `token`, requiring that the link associated with the record
    # be visited no more than five minutes prior; if the forgotpassword record is
    # not found or does not meet this requirement, raise an exception
    query = d.engine.execute("""
        SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address
        FROM login lo
            INNER JOIN userinfo ui USING (userid)
            INNER JOIN forgotpassword fp USING (userid)
        WHERE fp.token = %(token)s AND fp.link_time > %(cutoff)s
    """,
                             token=form.token,
                             cutoff=d.get_time() - 300).first()

    if not query:
        raise WeasylError("forgotpasswordRecordMissing")

    USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query

    # Check `username` and `email` against known correct values and raise an
    # exception if there is a mismatch
    if emailer.normalize_address(
            form.email) != emailer.normalize_address(EMAIL):
        raise WeasylError("emailIncorrect")
    elif d.get_sysname(form.username) != USERNAME:
        raise WeasylError("usernameIncorrect")
    elif d.get_address() != ADDRESS:
        raise WeasylError("addressInvalid")

    # Update the authbcrypt table with a new password hash
    d.engine.execute(
        'INSERT INTO authbcrypt (userid, hashsum) VALUES (%(user)s, %(hash)s) '
        'ON CONFLICT (userid) DO UPDATE SET hashsum = %(hash)s',
        user=USERID,
        hash=login.passhash(form.password))

    d.engine.execute("DELETE FROM forgotpassword WHERE token = %(token)s",
                     token=form.token)
Exemplo n.º 8
0
def force(userid, form):
    from weasyl import login

    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    d.engine.execute(
        "UPDATE login SET force_password_reset = FALSE WHERE userid = %(user)s",
        user=userid)
    d.engine.execute(
        "UPDATE authbcrypt SET hashsum = %(new_hash)s WHERE userid = %(user)s",
        new_hash=login.passhash(form.password),
        user=userid)
    d._get_all_config.invalidate(userid)
Exemplo n.º 9
0
def reset(form):
    from weasyl import login

    # Raise an exception if `password` does not enter `passcheck` (indicating
    # that the user mistyped one of the fields) or if `password` does not meet
    # the system's password security requirements
    if form.password != form.passcheck:
        raise WeasylError("passwordMismatch")
    elif not login.password_secure(form.password):
        raise WeasylError("passwordInsecure")

    # Select the user information and record data from the forgotpassword table
    # pertaining to `token`, requiring that the link associated with the record
    # be visited no more than five minutes prior; if the forgotpassword record is
    # not found or does not meet this requirement, raise an exception
    query = d.execute("""
        SELECT lo.userid, lo.login_name, lo.email, fp.link_time, fp.address
        FROM login lo
            INNER JOIN userinfo ui USING (userid)
            INNER JOIN forgotpassword fp USING (userid)
        WHERE fp.token = '%s' AND fp.link_time > %i
    """, [form.token, d.get_time() - 300], options="single")

    if not query:
        raise WeasylError("forgotpasswordRecordMissing")

    USERID, USERNAME, EMAIL, LINKTIME, ADDRESS = query

    # Check `username` and `email` against known correct values and raise an
    # exception if there is a mismatch
    if emailer.normalize_address(form.email) != emailer.normalize_address(EMAIL):
        raise WeasylError("emailIncorrect")
    elif d.get_sysname(form.username) != USERNAME:
        raise WeasylError("usernameIncorrect")
    elif d.get_address() != ADDRESS:
        raise WeasylError("addressInvalid")

    # Update the authbcrypt table with a new password hash
    d.engine.execute(
        'INSERT INTO authbcrypt (userid, hashsum) VALUES (%(user)s, %(hash)s) '
        'ON CONFLICT (userid) DO UPDATE SET hashsum = %(hash)s',
        user=USERID, hash=login.passhash(form.password))

    d.execute("DELETE FROM forgotpassword WHERE token = '%s'", [form.token])
Exemplo n.º 10
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
Exemplo n.º 11
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