]> git.ipfire.org Git - ipfire.org.git/commitdiff
people: Implement password reset
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 31 Oct 2019 17:23:47 +0000 (17:23 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 31 Oct 2019 17:23:47 +0000 (17:23 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/accounts.py
src/templates/auth/password-reset.html [new file with mode: 0644]
src/web/__init__.py
src/web/auth.py

index 6d4c8e5f0bdff4b45e116cabdab49ac87c66c4a5..08ed643b63f89c7e9807adddbf159f596754245f 100644 (file)
@@ -113,6 +113,7 @@ templates_auth_DATA = \
        src/templates/auth/activate.html \
        src/templates/auth/activated.html \
        src/templates/auth/login.html \
+       src/templates/auth/password-reset.html \
        src/templates/auth/password-reset-initiation.html \
        src/templates/auth/password-reset-successful.html \
        src/templates/auth/register.html \
index 341709e2125e02cdbcd6174c6548fdb5dac02bc3..921d38cfaf349019e22eba1dd840b4ac67a59b9d 100644 (file)
@@ -603,7 +603,7 @@ class Account(Object):
                        self.first_name, self.last_name,
                ))
 
-       def reset_password(self, address=None):
+       def request_password_reset(self, address=None):
                reset_code = util.random_string(64)
 
                self.db.execute("INSERT INTO account_password_resets(uid, reset_code, address) \
@@ -613,6 +613,19 @@ class Account(Object):
                self.backend.messages.send_template("auth/messages/password-reset",
                        recipients=[self.email], priority=100, account=self, reset_code=reset_code)
 
+       def reset_password(self, reset_code, new_password):
+               # Delete the reset token
+               res = self.db.query("DELETE FROM account_password_resets \
+                       WHERE uid = %s AND reset_code = %s AND expires_at >= NOW() \
+                       RETURNING *", self.uid, reset_code)
+
+               # The reset code was invalid
+               if not res:
+                       raise ValueError("Invalid password reset token for %s: %s" % (self, reset_code))
+
+               # Perform password change
+               return self.passwd(new_password)
+
        def is_admin(self):
                return self.is_member_of_group("sudo")
 
diff --git a/src/templates/auth/password-reset.html b/src/templates/auth/password-reset.html
new file mode 100644 (file)
index 0000000..3a26a17
--- /dev/null
@@ -0,0 +1,21 @@
+{% extends "../base.html" %}
+
+{% block title %}{{ _("Password Reset") }}{% end block %}
+
+{% block content %}
+       <div class="row justify-content-center my-5">
+               <div class="col col-md-4">
+                       <h5 class=" mb-4">{{ _("Reset Your Password") }}</h5>
+
+                       <form action="" method="POST">
+                               {% raw xsrf_form_html() %}
+
+                               {% module Password(account) %}
+
+                               <button type="submit" class="btn btn-primary btn-block" disabled>
+                                       {{ _("Reset Password") }}
+                               </button>
+                       </form>
+               </div>
+       </div>
+{% end block %}
index 4acb0dfc325e797d04a0a51932d3a82436448a5d..76d23edd4298bbc0da79dd99483cf0cb48eff8e6 100644 (file)
@@ -288,7 +288,8 @@ class Application(tornado.web.Application):
                        (r"/sso/discourse", people.SSODiscourse),
 
                        # Password Reset
-                       (r"/password-reset", auth.PasswordResetInitiationHandler),
+                       (r"/password\-reset", auth.PasswordResetInitiationHandler),
+                       (r"/password\-reset/([a-z_][a-z0-9_-]{0,31})/(\w+)", auth.PasswordResetHandler),
 
                        # API
                        (r"/api/check/uid", auth.APICheckUID),
index e3fb5f13fe3e3de7e4509b913e26bdae74973c4f..4bd563ffec66ec11f5ca20cd0ac53aec3d69f156 100644 (file)
@@ -147,7 +147,7 @@ class ActivateHandler(AuthenticationMixin, base.BaseHandler):
                self.render("auth/activated.html", account=account)
 
 
-class PasswordResetInitiationHandler(AuthenticationMixin, base.BaseHandler):
+class PasswordResetInitiationHandler(base.BaseHandler):
        def get(self):
                username = self.get_argument("username", None)
 
@@ -161,11 +161,41 @@ class PasswordResetInitiationHandler(AuthenticationMixin, base.BaseHandler):
                account = self.backend.accounts.get_by_uid(username)
                if account:
                        with self.db.transaction():
-                               account.reset_password()
+                               account.request_password_reset()
 
                self.render("auth/password-reset-successful.html")
 
 
+class PasswordResetHandler(AuthenticationMixin, base.BaseHandler):
+       def get(self, uid, reset_code):
+               account = self.backend.accounts.get_by_uid(uid)
+               if not account:
+                       raise tornado.web.HTTPError(404, "Could not find account: %s" % uid)
+
+               self.render("auth/password-reset.html", account=account)
+
+       def post(self, uid, reset_code):
+               account = self.backend.accounts.get_by_uid(uid)
+               if not account:
+                       raise tornado.web.HTTPError(404, "Could not find account: %s" % uid)
+
+               password1 = self.get_argument("password1")
+               password2 = self.get_argument("password2")
+
+               if not password1 == password2:
+                       raise tornado.web.HTTPError(400, "Passwords do not match")
+
+               # Try to perform password reset
+               with self.db.transaction():
+                       account.reset_password(reset_code, password1)
+
+                       # Login the user straight away after reset was successful
+                       self.login(account)
+
+               # Redirect back to /
+               self.redirect("/")
+
+
 class APICheckUID(base.APIHandler):
        @base.ratelimit(minutes=10, requests=100)
        def get(self):