]> git.ipfire.org Git - ipfire.org.git/commitdiff
accounts: Build a way for users to change their own email address
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 27 Nov 2024 14:30:17 +0000 (14:30 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 27 Nov 2024 14:30:17 +0000 (14:30 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/backend/accounts.py
src/templates/auth/email-confirmed.html [new file with mode: 0644]
src/templates/auth/messages/change-email.html [new file with mode: 0644]
src/templates/auth/messages/change-email.txt [new file with mode: 0644]
src/templates/users/edit.html
src/web/__init__.py
src/web/auth.py
src/web/users.py

index 18b6926f8bb92bf23fbfb269c2ff80a6f69a221d..f84a375e62ce24c9ad9cb749e3d68a25c6d8dad1 100644 (file)
@@ -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 \
index 8366a01ef9580e1b992b80a9e99f2c3c74947050..8928033ceb8ca4edd6ffa0b36a09784b1d246940 100644 (file)
@@ -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 (file)
index 0000000..d7d6827
--- /dev/null
@@ -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 %}
+       <section class="section">
+               <div class="container">
+                       <div class="columns is-centered">
+                               <div class="column is-half">
+                                       {% if success %}
+                                               <div class="notification is-success has-text-centered">
+                                                       <h1 class="title">
+                                                               {{ _("Thank You!") }}
+                                                       </h1>
+
+                                                       <h4 class="subtitle">
+                                                               {{ _("Your email address has successfully been changed") }}
+                                                       </h4>
+
+                                                       <div class="block has-text-centered">
+                                                               <span class="icon m-5">
+                                                                       <i class="fas fa-check fa-5x"></i>
+                                                               </span>
+                                                       </div>
+                                               </div>
+                                       {% else %}
+                                               <div class="notification is-danger has-text-centered">
+                                                       <h1 class="title">
+                                                               {{ _("Sorry") }}
+                                                       </h1>
+
+                                                       <h4 class="subtitle">
+                                                               {{ _("We could not change your email address") }}
+                                                       </h4>
+
+                                                       <div class="block has-text-centered">
+                                                               <span class="icon m-5">
+                                                                       <i class="fas fa-xmark fa-5x"></i>
+                                                               </span>
+                                                       </div>
+                                               </div>
+                                       {% end %}
+                               </div>
+                       </div>
+               </div>
+       </section>
+{% end block %}
diff --git a/src/templates/auth/messages/change-email.html b/src/templates/auth/messages/change-email.html
new file mode 100644 (file)
index 0000000..21688b4
--- /dev/null
@@ -0,0 +1,24 @@
+{% extends "../../messages/base.html" %}
+
+{% block content %}
+       <tr class="section">
+               <td>
+               <h1>{{ _("Hello %s!") % account.first_name }}</h1>
+
+                       <p>
+                               {{ _("You, or somebody else on your behalf, has requested to change your email address.") }}
+                               {{ _("If this was not you, please notify a team member.") }}
+                       </p>
+
+                       <table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0">
+                               <tr class="button">
+                                       <td>
+                                                <a class="primary" href="https://www.ipfire.org/confirm-email/{{ account.uid }}/{{ token }}">
+                                                       {{ _("Confirm Email Address") }}
+                                                </a>
+                                       </td>
+                               </tr>
+                       </table>
+               </td>
+       </tr>
+{% end block %}
diff --git a/src/templates/auth/messages/change-email.txt b/src/templates/auth/messages/change-email.txt
new file mode 100644 (file)
index 0000000..66b4345
--- /dev/null
@@ -0,0 +1,11 @@
+From: IPFire Project <no-reply@ipfire.org>
+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 }}
index ac2e8091b478f0953db12d583a6bcff956b95aaf..4273747f65dcceb1b47646313a77af34766ca9ae 100644 (file)
                                                </div>
 
                                                {# Email #}
+                                               <div class="field">
+                                                       <label class="label">{{ _("Email Address") }}</label>
+
+                                                       <div class="control">
+                                                               <input type="mail" class="input" name="email"
+                                                                       placeholder="{{ _("Email Address") }}"
+                                                                       value="{{ account.email or "" }}" required>
+                                                       </div>
+                                               </div>
+
                                                {% if account.has_mail() %}
                                                        <div class="field">
                                                                <label class="label">{{ _("Alternate Email Addresses") }}</label>
index fda68bdaadb9993e26192bfbf63567fa6702a9db..2d0efca4efb5d6e466f7046776f9c22d2db2b6a1 100644 (file)
@@ -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),
index 9e81631c4e8b329c2fa5fdfc91dedbf10c2b3a4e..c3a7d48a824dfcffbed8dd0b1a2af20cd21823d3 100644 (file)
@@ -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):
index 7342258787cf78d91d8fdafb489fa32292f24467..7bd4714e5bf405c6243aa43ea2f234b76db9b7f8 100644 (file)
@@ -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)