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