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")
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"))
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])
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)
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, } }))
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)
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)
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)
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])
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
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