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 \
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) \
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")
--- /dev/null
+{% 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 %}
(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),
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)
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):