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)))
def request(form): token = security.generate_key(100) email = emailer.normalize_address(form.email) # Determine the user associated with `username`; if the user is not found, # raise an exception user_id = d.engine.scalar(""" SELECT userid FROM login WHERE email = %(email)s """, email=email) # If `user_id` exists, then the supplied email was valid; if not valid, do nothing, raising # no errors for plausible deniability of email existence if user_id: # Insert a record into the forgotpassword table for the user, # or update an existing one now = d.get_time() address = d.get_address() d.engine.execute(""" INSERT INTO forgotpassword (userid, token, set_time, address) VALUES (%(id)s, %(token)s, %(time)s, %(address)s) ON CONFLICT (userid) DO UPDATE SET token = %(token)s, set_time = %(time)s, address = %(address)s """, id=user_id, token=token, time=now, address=address) # Generate and send an email to the user containing a password reset link emailer.send(email, "Weasyl Password Recovery", d.render("email/reset_password.html", [token]))
def request(email): token = security.generate_key(25, key_characters=string.digits + string.ascii_lowercase) token_sha256 = _hash_token(token) email = emailer.normalize_address(email) if email is None: raise WeasylError("emailInvalid") d.engine.execute( "INSERT INTO forgotpassword (email, token_sha256)" " VALUES (%(email)s, %(token_sha256)s)", email=email, token_sha256=bytearray(token_sha256)) # Generate and send an email to the user containing a password reset link emailer.send(email, "Weasyl Account Recovery", d.render("email/reset_password.html", [token]))
def create(form): # Normalize form data username = clean_display_name(form.username) sysname = d.get_sysname(username) email = emailer.normalize_address(form.email) emailcheck = emailer.normalize_address(form.emailcheck) password = form.password passcheck = form.passcheck if form.day and form.month and form.year: try: birthday = arrow.Arrow(int(form.year), int(form.month), int(form.day)) except ValueError: raise WeasylError("birthdayInvalid") else: birthday = None # Check mismatched form data if password != passcheck: raise WeasylError("passwordMismatch") if email != emailcheck: raise WeasylError("emailMismatch") # Check invalid form data if birthday is None or d.age_in_years(birthday) < 13: raise WeasylError("birthdayInvalid") if not password_secure(password): raise WeasylError("passwordInsecure") if not email: raise WeasylError("emailInvalid") if is_email_blacklisted(email): raise WeasylError("emailBlacklisted") if username_exists(sysname): raise WeasylError("usernameExists") # Account verification token token = security.generate_key(40) # Only attempt to create the account if the email is unused (as defined by the function) if not email_exists(email): # Create pending account d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": email, "birthday": birthday, }) # Send verification email emailer.send(email, "Weasyl Account Creation", d.render( "email/verify_account.html", [token, sysname])) d.metric('increment', 'createdusers') else: # Store a dummy record to support plausible deniability of email addresses # So "reserve" the username, but mark the record invalid, and use the token to satisfy the uniqueness # constraint for the email field (e.g., if there is already a valid, pending row in the table). d.engine.execute(d.meta.tables["logincreate"].insert(), { "token": token, "username": username, "login_name": sysname, "hashpass": passhash(password), "email": token, "birthday": arrow.now(), "invalid": True, # So we have a way for admins to determine which email address collided in the View Pending Accounts Page "invalid_email_addr": email, }) # The email address in question is already in use in either `login` or `logincreate`; # let the already registered user know this via email (perhaps they forgot their username/password) query_username_login = d.engine.scalar("SELECT login_name FROM login WHERE email = %(email)s", email=email) query_username_logincreate = d.engine.scalar("SELECT login_name FROM logincreate WHERE email = %(email)s", email=email) emailer.send(email, "Weasyl Account Creation - Account Already Exists", d.render( "email/email_in_use_account_creation.html", [query_username_login or query_username_logincreate]))
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. newpasscheck: 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.send( newemail, "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.send( newemail, "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.engine.execute( "UPDATE authbcrypt SET hashsum = %(new_hash)s WHERE userid = %(user)s", new_hash=login.passhash(newpassword), user=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