From: Michael Tremer Date: Wed, 27 Nov 2024 14:30:17 +0000 (+0000) Subject: accounts: Build a way for users to change their own email address X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fb163874e088bdfd2acf95210bbdf44ee69e2422;p=ipfire.org.git accounts: Build a way for users to change their own email address Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 18b6926f..f84a375e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -129,6 +129,7 @@ templates_analytics_modulesdir = $(templates_analyticsdir)/modules templates_auth_DATA = \ src/templates/auth/activate.html \ src/templates/auth/activated.html \ + src/templates/auth/email-confirmed.html \ src/templates/auth/join.html \ src/templates/auth/join-success.html \ src/templates/auth/login.html \ @@ -139,6 +140,8 @@ templates_auth_DATA = \ templates_authdir = $(templatesdir)/auth templates_auth_messages_DATA = \ + src/templates/auth/messages/change-email.html \ + src/templates/auth/messages/change-email.txt \ src/templates/auth/messages/donation-reminder.html \ src/templates/auth/messages/donation-reminder.txt \ src/templates/auth/messages/join.html \ diff --git a/src/backend/accounts.py b/src/backend/accounts.py index 8366a01e..8928033c 100644 --- a/src/backend/accounts.py +++ b/src/backend/accounts.py @@ -1095,6 +1095,58 @@ class Account(LDAPObject): def email(self): return self._get_string("mail") + def change_email(self, address, who=None): + """ + Changes the email address + """ + # Do nothing if the email address has not changed + if self.email == address: + return + + # Store the email address straight away if we are admins + if who is None or who.is_admin(): + self._set_string("mail", address) + return + + # Generate a token + token = util.random_string(64) + + # Store the token + self.db.execute("INSERT INTO account_email_changes(uid, token, address) \ + VALUES(%s, %s, %s)", self.uid, token, address) + + # Otherwise we send a token to the new email address for activation + self.send_message("auth/messages/change-email", priority=100, headers={ + "To" : email.utils.formataddr((self.nickname or self.name, address)) + }, token=token) + + def confirm_email(self, token): + """ + Called when a user is confirming their new email address + """ + res = self.db.get(""" + DELETE FROM + account_email_changes + WHERE + uid = %s + AND + token = %s + AND + expires_at >= CURRENT_TIMESTAMP + RETURNING + address + """, self.uid, token, + ) + + # If the token does not exist we cannot proceed + if not res: + return False + + # Change the email address + self.change_email(res.address) + + return True + @property def email_to(self): return email.utils.formataddr((self.nickname or self.name, self.email)) diff --git a/src/templates/auth/email-confirmed.html b/src/templates/auth/email-confirmed.html new file mode 100644 index 00000000..d7d6827b --- /dev/null +++ b/src/templates/auth/email-confirmed.html @@ -0,0 +1,53 @@ +{% extends "../base.html" %} + +{% block title %} + {% if success %} + {{ _("Email Address Confirmed") }} + {% else %} + {{ _("Could Not Confirm Email Address") }} + {% end %} +{% end block %} + +{% block content %} +
+
+
+
+ {% if success %} +
+

+ {{ _("Thank You!") }} +

+ +

+ {{ _("Your email address has successfully been changed") }} +

+ +
+ + + +
+
+ {% else %} +
+

+ {{ _("Sorry") }} +

+ +

+ {{ _("We could not change your email address") }} +

+ +
+ + + +
+
+ {% end %} +
+
+
+
+{% end block %} diff --git a/src/templates/auth/messages/change-email.html b/src/templates/auth/messages/change-email.html new file mode 100644 index 00000000..21688b45 --- /dev/null +++ b/src/templates/auth/messages/change-email.html @@ -0,0 +1,24 @@ +{% extends "../../messages/base.html" %} + +{% block content %} + + +

{{ _("Hello %s!") % account.first_name }}

+ +

+ {{ _("You, or somebody else on your behalf, has requested to change your email address.") }} + {{ _("If this was not you, please notify a team member.") }} +

+ + + + + +
+ + {{ _("Confirm Email Address") }} + +
+ + +{% end block %} diff --git a/src/templates/auth/messages/change-email.txt b/src/templates/auth/messages/change-email.txt new file mode 100644 index 00000000..66b43455 --- /dev/null +++ b/src/templates/auth/messages/change-email.txt @@ -0,0 +1,11 @@ +From: IPFire Project +Subject: {{ _("Confirm Your New Email Address") }} +X-Auto-Response-Suppress: OOF + +{{ _("Hello %s!") % account.first_name }} + +{{ _("You, or somebody else on your behalf, has requested to change your email address.") }} {{ _("If this was not you, please notify a team member.") }} + +{{ _("Please click on this link to confirm your email address:") }} + + https://www.ipfire.org/confirm-email/{{ account.uid }}/{{ token }} diff --git a/src/templates/users/edit.html b/src/templates/users/edit.html index ac2e8091..4273747f 100644 --- a/src/templates/users/edit.html +++ b/src/templates/users/edit.html @@ -203,6 +203,16 @@ {# Email #} +
+ + +
+ +
+
+ {% if account.has_mail() %}
diff --git a/src/web/__init__.py b/src/web/__init__.py index fda68bda..2d0efca4 100644 --- a/src/web/__init__.py +++ b/src/web/__init__.py @@ -191,6 +191,9 @@ class Application(tornado.web.Application): # Lists (r"/lists", lists.IndexHandler), + # Confirm Email + (r"/confirm-email/([a-z_][a-z0-9_-]{0,31})/(\w+)", auth.ConfirmEmailHandler), + # Password Reset (r"/password\-reset", auth.PasswordResetInitiationHandler), (r"/password\-reset/([a-z_][a-z0-9_-]{0,31})/(\w+)", auth.PasswordResetHandler), diff --git a/src/web/auth.py b/src/web/auth.py index 9e81631c..c3a7d48a 100644 --- a/src/web/auth.py +++ b/src/web/auth.py @@ -184,6 +184,18 @@ class PasswordResetHandler(AuthenticationMixin, base.BaseHandler): self.redirect("/") +class ConfirmEmailHandler(base.BaseHandler): + def get(self, uid, token): + account = self.backend.accounts.get_by_uid(uid) + if not account: + raise tornado.web.HTTPError(404, "Could not find account: %s" % uid) + + with self.db.transaction(): + success = account.confirm_email(token) + + self.render("auth/email-confirmed.html", success=success) + + class WellKnownChangePasswordHandler(base.BaseHandler): @tornado.web.authenticated def get(self): diff --git a/src/web/users.py b/src/web/users.py index 73422587..7bd4714e 100644 --- a/src/web/users.py +++ b/src/web/users.py @@ -197,6 +197,9 @@ class EditHandler(base.BaseHandler): pass # Email + account.change_email( + address=self.get_argument("email"), who=self.current_user, + ) account.alternate_email_addresses = self.get_argument("alternate_email_addresses", "").split() account.mail_routing_address = self.get_argument("mail_routing_address", None)