Exemplo n.º 1
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)))
Exemplo n.º 2
0
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]))
Exemplo n.º 3
0
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]))
Exemplo n.º 4
0
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]))
Exemplo n.º 5
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.
        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