首页 > 解决方案 > Flask - 重置密码不会修改 SQLite 数据库中的值

问题描述

我是 Python 和 Flask(甚至 SO)的初学者,所以请原谅我下面的标准代码,如果我的问题缺少任何必需的细节,请随时通知我。尝试寻找答案(阅读许多类似的教程)但没有成功。根据我的理解,这是与 SO 最接近的匹配,但对我不起作用。

我的应用程序有SQLite DB 和Flask-Login用于身份验证。我正在尝试为注册用户重置密码。所以用户点击登录页面上的“忘记密码”按钮(如果用户没有注册,他会被路由到注册页面),这会导致另一个页面,我要求输入注册的电子邮件 ID。向用户发送一封带有验证链接的电子邮件,一旦他/她点击该链接,就会转到密码重置页面。

据我了解,此密码重置页面(相关视图)正在产生问题。在这里,用户输入新密码,但在我的数据库中没有更新。重置后,成功消息确实会发生到登录页面的预期路由,但实际上当我尝试使用新密码登录时,它会失败,因为它仍然使用旧密码进行身份验证。虽然也有一个 DateTime 值,但我在密码重置期间同时尝试输入该值,并且该条目是成功的。

希望我能很好地传达我的疑问。以下是我为此密码重置过程创建的3 个视图:

# View for Password Reset form:
@app.route("/password_reset", methods=["GET","POST"])
def password_reset():
    form = PasswordResetForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user is None:
            flash(u"Invalid/Unknown email address.")
            return render_template("password_reset.html", form=form)
        elif user is not None and form.new_password.data != form.new_pass_confirm.data:
            flash(u"Password mismatch!")
            return render_template("password_reset.html", form=form)
        else:
            user.passwordUpdated_on = datetime.now()
            user.password = form.new_password.data  #This is my problem line, I guess.
            db.session.add(user)
            db.session.commit()
            flash("Password has been successfully updated!")
            return redirect(url_for("login"))
    return render_template("password_reset.html", form=form)


# Helper function to redirect User after clicking on password reset link:
@app.route("/reset/<token>")
def pwdreset_email(token):
    try:
        email = pwdreset_token(token)
    except:
        flash("Your password reset link is invalid or has expired.")
        return redirect(url_for("support"))
    return redirect(url_for("password_reset"))


# User Registration/Signup View:
@app.route("/forgot_password", methods=["GET","POST"])
def forgot_password():
    form = ForgotPasswordForm()
    if form.validate_on_submit():
        # If User is registered with us:
        user = User.query.filter_by(email=form.email.data).first()
        if user is None:
            flash(u"Unknown email address!")
            return render_template("forgot_password.html", form=form)
        # If User is registered and confirmed, sending Password Reset email:
        if user.confirmed:
            token = generate_pwdreset_token(user.email)
            reset_url = url_for("pwdreset_email", token=token, _external=True)
            html = render_template("password_email.html", confirm_url=reset_url)
            subject = "Password Reset!"
            send_email(user.email, subject, html)
            db.session.add(user)
            db.session.commit()
            flash(u"Kindly check registered email for a password reset link!")
            # Routing User to Login page:
            return redirect(url_for("login"))
        elif user.confirmed is False:
            flash(u"Your email address must be confirmed before attempting a password reset.")
            return redirect(url_for("unconfirmed"))
    # Rendering a template for User to initiate Password Reset:
    return render_template("forgot_password.html", form=form)

这是我的模型

class User(db.Model, UserMixin):

    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True, nullable=False)
    username = db.Column(db.String(64), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)
    passwordUpdated_on = db.Column(db.DateTime, nullable=True)
    confirmed = db.Column(db.Boolean, nullable=False, default=False)

    def __init__(self, email, username, password, passwordUpdated_on=None, confirmed=False):
        self.email = email
        self.username = username
        self.password_hash = generate_password_hash(password) #Werkzeug
        self.passwordUpdated_on = passwordUpdated_on
        self.confirmed = confirmed

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

这是我的配置脚本:

class BaseConfig(object):
    """
    Base configuration for Database and Mail settings.
    """

    # Creating Database with preferred settings:
    basedir = abspath(dirname(__file__))
    SQLALCHEMY_DATABASE_URI = "sqlite:///" + join(basedir, "my_data.sqlite")
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SECURITY_RECOVERABLE = True  # Added this looking at other SO answer. Haven't yet read about it.

    # Main Application configuration:
    SECRET_KEY = "random_key"
    SECURITY_PASSWORD_SALT = "random_password"
    WTF_CSRF_ENABLED = True
    DEBUG_TB_ENABLED = False
    DEBUG_TB_INTERCEPT_REDIRECTS = False

最后是我的表格

class ForgotPasswordForm(FlaskForm):
    email = StringField("Email Address: ", validators=[DataRequired()])
    submit = SubmitField("Reset Password")


class PasswordResetForm(FlaskForm):
    email = StringField("Email Address: ", validators=[DataRequired()])
    new_password = PasswordField("New Password: ", validators=[DataRequired(), EqualTo("new_pass_confirm")])
    new_pass_confirm = PasswordField("Confirm New Password: ", validators=[DataRequired()])
    submit = SubmitField("Update Password")

还有我的 password_reset模板如下:

<form action="" method="POST">
  {{ form.hidden_tag() }}

  <div class="form-group">
    <label for="email">Email Address: </label>
    <input type="email" class="form-control form-control-sm" name="email" id="email" aria-describedby="emailHelp" value="">
  </div>

  <div class="form-group">
    <label for="new_password"><h5 style="font-family:verdana; color: #514e0d"><b>New Password: </b></h5></label>
    <input type="password" class="form-control form-control-sm" name="new_password" id="new_password" value="">
  </div>

  <div class="form-group">
    <label for="new_pass_confirm">Confirm New Password: </label>
    <input type="password" class="form-control form-control-sm" name="new_pass_confirm" id="new_pass_confirm" value="">
  </div>

  <div class="row">
    <div class="col">
      <a class="btn btn-warning btn-lg" href="{{ url_for("support") }}" role="button">Support </a>
    </div>
    <div class="col">
      <button type="submit" class="btn btn-success btn-lg float-right">Update Password</button>
    </div>
  </div>
  <br>

</form>

任何线索将不胜感激。再次感谢您的宝贵时间,如果我错过了提供任何必需的信息,请告诉我。

解决方案: 在我的models.py中,我添加了:

@property
def password(self):
    """
    The password property will call werkzeug.security and
    write the result to the 'password_hash' field.
    Reading this property will return an error.
    """
    raise AttributeError("password is not a readable attribute")

@password.setter
def password(self, password):
    self.password_hash = generate_password_hash(password)

标签: pythonflaskflask-sqlalchemyflask-wtformsflask-login

解决方案


您正在设置user.password,但模型没有该属性。password_hash是您需要设置的字段。

此外,您似乎正在尝试保存密码本身;如果是这样,这是不正确的。您需要保存密码的哈希值,而不是密码本身。所以这条线应该看起来更像这样(很难说这是否完全正确,但这是正确的想法)。

user.password_hash = generate_password_hash(password) 

推荐阅读