mirror of https://github.com/zulip/zulip.git
change-email: Implement confirmation flow.
This adds to Zulip support for a user changing their own email address. It's backed by a huge amount of work by Steve Howell on making email changes actually work from a UI perspective. Fixes #734.
This commit is contained in:
parent
1929cc5190
commit
5bf83f9e0a
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-17 09:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('confirmation', '0002_realmcreationkey'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EmailChangeConfirmation',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
},
|
||||
bases=('confirmation.confirmation',),
|
||||
),
|
||||
]
|
|
@ -19,7 +19,7 @@ from django.utils.timezone import now
|
|||
|
||||
from confirmation.util import get_status_field
|
||||
from zerver.lib.utils import generate_random_token
|
||||
from zerver.models import PreregistrationUser
|
||||
from zerver.models import PreregistrationUser, EmailChangeStatus
|
||||
from typing import Optional, Union, Any, Text
|
||||
|
||||
B16_RE = re.compile('^[a-f0-9]{40}$')
|
||||
|
@ -59,7 +59,7 @@ def generate_realm_creation_url():
|
|||
class ConfirmationManager(models.Manager):
|
||||
|
||||
def confirm(self, confirmation_key):
|
||||
# type: (str) -> Union[bool, PreregistrationUser]
|
||||
# type: (str) -> Union[bool, PreregistrationUser, EmailChangeStatus]
|
||||
if B16_RE.search(confirmation_key):
|
||||
try:
|
||||
confirmation = self.get(confirmation_key=confirmation_key)
|
||||
|
@ -140,6 +140,20 @@ class ConfirmationManager(models.Manager):
|
|||
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [email_address], html_message=html_content)
|
||||
return self.create(content_object=obj, date_sent=now(), confirmation_key=confirmation_key)
|
||||
|
||||
class EmailChangeConfirmationManager(ConfirmationManager):
|
||||
def get_activation_url(self, key, host=None):
|
||||
# type: (Text, Optional[str]) -> Text
|
||||
if host is None:
|
||||
# This will raise exception if the key doesn't exist.
|
||||
host = self.get(confirmation_key=key).content_object.realm.host
|
||||
return u'%s%s%s' % (settings.EXTERNAL_URI_SCHEME,
|
||||
host,
|
||||
reverse('zerver.views.user_settings.confirm_email_change',
|
||||
kwargs={'confirmation_key': key}))
|
||||
|
||||
def get_link_validity_in_days(self):
|
||||
# type: () -> int
|
||||
return settings.EMAIL_CHANGE_CONFIRMATION_DAYS
|
||||
|
||||
class Confirmation(models.Model):
|
||||
content_type = models.ForeignKey(ContentType)
|
||||
|
@ -158,6 +172,12 @@ class Confirmation(models.Model):
|
|||
# type: () -> Text
|
||||
return _('confirmation email for %s') % (self.content_object,)
|
||||
|
||||
class EmailChangeConfirmation(Confirmation):
|
||||
class Meta(object):
|
||||
proxy = True
|
||||
|
||||
objects = EmailChangeConfirmationManager()
|
||||
|
||||
class RealmCreationKey(models.Model):
|
||||
creation_key = models.CharField(_('activation key'), max_length=40)
|
||||
date_created = models.DateTimeField(_('created'), default=now)
|
||||
|
|
|
@ -517,12 +517,44 @@ function _setup_page() {
|
|||
});
|
||||
});
|
||||
|
||||
$('#change_email_button').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$('#change_email_modal').modal('hide');
|
||||
|
||||
var data = {};
|
||||
data.email = $('.email_change_container').find("input[name='email']").val();
|
||||
|
||||
channel.patch({
|
||||
url: '/json/settings/change',
|
||||
data: data,
|
||||
success: function (data) {
|
||||
if ('account_email' in data) {
|
||||
settings_change_success(data.account_email);
|
||||
} else {
|
||||
settings_change_error(i18n.t("Error changing settings: No new data supplied."));
|
||||
}
|
||||
},
|
||||
error: function (xhr) {
|
||||
settings_change_error("Error changing settings", xhr);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$('#default_language').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$('#default_language_modal').show().attr('aria-hidden', false);
|
||||
});
|
||||
|
||||
$('#change_email').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$('#change_email_modal').modal('show');
|
||||
var email = $('#email_value').text();
|
||||
$('.email_change_container').find("input[name='email']").val(email);
|
||||
});
|
||||
|
||||
$("#user_deactivate_account_button").on('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -4,6 +4,31 @@
|
|||
{{t "Your account" }}
|
||||
</div>
|
||||
<div class="account-settings-form">
|
||||
<form class="email-change-form">
|
||||
<p for="change_email" class="inline-block title">
|
||||
{{t "Email" }}: <span id='email_value'>{{page_params.email}}</span>
|
||||
<a id="change_email" href="#change_email" title="{{t 'Change email' }}">[Change]</a>
|
||||
</p>
|
||||
|
||||
<div id="change_email_modal" class="modal hide" tabindex="-1" role="dialog"
|
||||
aria-labelledby="change_email_modal_label" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="change_email_modal_label">{{t "Change email" }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="input-group email_change_container">
|
||||
<label for="email">{{t "Email" }}</label>
|
||||
<input type="text" name="email" value="{{ page_params.email }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id='change_email_button' class="btn btn-success" data-dismiss="modal" aria-hidden="true">{{t "Change" }}</button>
|
||||
<button class="btn btn-primary" data-dismiss="modal" aria-hidden="true">{{t "Close" }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form action="/json/settings/change" method="post"
|
||||
class="form-horizontal your-account-settings">
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{% extends "zerver/portico.html" %}
|
||||
|
||||
{% block portico_content %}
|
||||
|
||||
<div class="pitch">
|
||||
<hr/>
|
||||
{% if confirmed %}
|
||||
<p>
|
||||
This confirms that the email address for your Zulip account has changed
|
||||
from {{old_email}} to {{ new_email }}.
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="lead">Whoops, something's not right. We couldn't find your confirmation ID!</p>
|
||||
|
||||
{% if verbose_support_offers %}
|
||||
<p>Make sure you copied the link correctly in to your browser. If you're
|
||||
still encountering this page, its probably our fault. We're sorry.</p>
|
||||
|
||||
<p>Anyway, shoot us a line at
|
||||
<a href="mailto:{{ support_email }}">{{ support_email }}</a>
|
||||
and we'll get this resolved shortly.
|
||||
</p>
|
||||
{% else %}
|
||||
<p>Make sure you copied the link correctly in to your browser.</p>
|
||||
|
||||
<p>If you're still having problems, please contact your Zulip administrator at
|
||||
<a href="mailto:{{ support_email }}">{{ support_email }}</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Zulip</title>
|
||||
</head>
|
||||
<body>
|
||||
<table width="80%" style="align:center; max-width:800px" align="center">
|
||||
<tr>
|
||||
<td style="font-size:16px; font-family:Helvetica;">
|
||||
<p>Hi!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We received a request to change the email address for the Zulip
|
||||
account on {{ realm.uri }} from {{ old_email }} to {{ new_email }}.
|
||||
If you would like to confirm this change, please click this link:
|
||||
<br />
|
||||
<a href="{{ activate_url }}" style="color:#08c">{{ activate_url }}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% if verbose_support_offers %}
|
||||
Feel free to give us a shout at
|
||||
<a href="mailto:{{ support_email }}" style="color:#08c">{{ support_email }}</a>
|
||||
if you have any questions or you did not request this change.
|
||||
{% else %}
|
||||
If you did not request this change, please contact the administrator
|
||||
of this Zulip server at
|
||||
<a href="mailto:{{ support_email }}" style="color:#08c">{{ support_email }}</a>.
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Cheers,<br />
|
||||
The Zulip Team
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
[Zulip] Confirm your new email address for {{ realm.name }}
|
|
@ -0,0 +1,19 @@
|
|||
Hi!
|
||||
|
||||
We received a request to change the email address for the Zulip account on
|
||||
{{ realm.uri }} from {{ old_email }} to {{ new_email }}. If you would like
|
||||
to confirm this change, please click this link:
|
||||
|
||||
{{ activate_url }}
|
||||
|
||||
{% if verbose_support_offers %}
|
||||
Feel free to give us a shout at <{{ support_email }}> if you have any
|
||||
questions or you did not request this change.
|
||||
{% else %}
|
||||
If you did not request this change, please contact the administrator
|
||||
of this Zulip server at <{{ support_email }}>.
|
||||
{% endif %}
|
||||
|
||||
Cheers,
|
||||
|
||||
The Zulip Team
|
|
@ -0,0 +1,9 @@
|
|||
Hi,
|
||||
|
||||
We just wanted to let you know that the email associated with your Zulip account
|
||||
was recently changed to {{ new_email }}. If you did not request this change,
|
||||
please contact us immediately at <{{ support_email }}>.
|
||||
|
||||
Best,
|
||||
|
||||
The Zulip Team
|
|
@ -0,0 +1 @@
|
|||
[Zulip] Email address changed for {{ realm.name }}
|
|
@ -375,6 +375,8 @@ def build_custom_checkers(by_lang):
|
|||
'return json_error(data=error_data, msg=ret_error)'),
|
||||
('zerver/views/streams.py', 'return json_error(property_conversion)'),
|
||||
('zerver/views/streams.py', 'return json_error(e.error, data=result, status=404)'),
|
||||
# error and skipped are already internationalized
|
||||
('zerver/views/user_settings.py', 'return json_error(error or skipped)'),
|
||||
# We can't do anything about this.
|
||||
('zerver/views/realm_filters.py', 'return json_error(e.messages[0], data={"errors": dict(e)})'),
|
||||
]),
|
||||
|
|
|
@ -37,7 +37,7 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
|
|||
realm_filters_for_realm, RealmFilter, receives_offline_notifications, \
|
||||
ScheduledJob, get_owned_bot_dicts, \
|
||||
get_old_unclaimed_attachments, get_cross_realm_emails, receives_online_notifications, \
|
||||
Reaction
|
||||
Reaction, EmailChangeStatus
|
||||
|
||||
from zerver.lib.alert_words import alert_words_in_realm
|
||||
from zerver.lib.avatar import avatar_url
|
||||
|
@ -50,7 +50,7 @@ from importlib import import_module
|
|||
from django.core.mail import EmailMessage
|
||||
from django.utils.timezone import now
|
||||
|
||||
from confirmation.models import Confirmation
|
||||
from confirmation.models import Confirmation, EmailChangeConfirmation
|
||||
import six
|
||||
from six.moves import filter
|
||||
from six.moves import map
|
||||
|
@ -708,6 +708,30 @@ def do_change_user_email(user_profile, new_email):
|
|||
'old_email': old_email,
|
||||
'new_email': new_email})
|
||||
|
||||
def do_start_email_change_process(user_profile, new_email):
|
||||
# type: (UserProfile, Text) -> None
|
||||
old_email = user_profile.email
|
||||
user_profile.email = new_email
|
||||
|
||||
context = {'support_email': settings.ZULIP_ADMINISTRATOR,
|
||||
'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS,
|
||||
'realm': user_profile.realm,
|
||||
'old_email': old_email,
|
||||
'new_email': new_email,
|
||||
}
|
||||
|
||||
with transaction.atomic():
|
||||
obj = EmailChangeStatus.objects.create(new_email=new_email,
|
||||
old_email=old_email,
|
||||
user_profile=user_profile,
|
||||
realm=user_profile.realm)
|
||||
|
||||
EmailChangeConfirmation.objects.send_confirmation(
|
||||
obj, new_email,
|
||||
additional_context=context,
|
||||
host=user_profile.realm.host,
|
||||
)
|
||||
|
||||
def compute_irc_user_fullname(email):
|
||||
# type: (NonBinaryStr) -> NonBinaryStr
|
||||
return email.split("@")[0] + " (IRC)"
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-23 05:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('zerver', '0052_auto_fix_realmalias_realm_nullable'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EmailChangeStatus',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('new_email', models.EmailField(max_length=254)),
|
||||
('old_email', models.EmailField(max_length=254)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('status', models.IntegerField(default=0)),
|
||||
('realm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
|
||||
('user_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -678,6 +678,18 @@ class PreregistrationUser(models.Model):
|
|||
|
||||
realm = models.ForeignKey(Realm, null=True) # type: Optional[Realm]
|
||||
|
||||
class EmailChangeStatus(models.Model):
|
||||
new_email = models.EmailField() # type: Text
|
||||
old_email = models.EmailField() # type: Text
|
||||
updated_at = models.DateTimeField(auto_now=True) # type: datetime.datetime
|
||||
user_profile = models.ForeignKey(UserProfile) # type: UserProfile
|
||||
|
||||
# status: whether an object has been confirmed.
|
||||
# if confirmed, set to confirmation.settings.STATUS_ACTIVE
|
||||
status = models.IntegerField(default=0) # type: int
|
||||
|
||||
realm = models.ForeignKey(Realm) # type: Realm
|
||||
|
||||
class PushDeviceToken(models.Model):
|
||||
APNS = 1
|
||||
GCM = 2
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
from typing import Any
|
||||
|
||||
import django
|
||||
import mock
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.http import HttpResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
|
||||
from confirmation.models import EmailChangeConfirmation, generate_key
|
||||
from zerver.lib.actions import do_start_email_change_process
|
||||
from zerver.lib.test_classes import (
|
||||
ZulipTestCase,
|
||||
)
|
||||
from zerver.models import get_user_profile_by_email, EmailChangeStatus, Realm
|
||||
|
||||
|
||||
class EmailChangeTestCase(ZulipTestCase):
|
||||
def test_confirm_email_change_with_non_existent_key(self):
|
||||
# type: () -> None
|
||||
self.login('hamlet@zulip.com')
|
||||
key = generate_key()
|
||||
with self.assertRaises(EmailChangeConfirmation.DoesNotExist):
|
||||
url = EmailChangeConfirmation.objects.get_activation_url(key)
|
||||
|
||||
url = EmailChangeConfirmation.objects.get_activation_url(
|
||||
key, 'testserver')
|
||||
response = self.client_get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Whoops", response.content.decode('utf8'))
|
||||
|
||||
def test_confirm_email_change_with_invalid_key(self):
|
||||
# type: () -> None
|
||||
self.login('hamlet@zulip.com')
|
||||
key = 'invalid key'
|
||||
with self.assertRaises(EmailChangeConfirmation.DoesNotExist):
|
||||
url = EmailChangeConfirmation.objects.get_activation_url(key)
|
||||
|
||||
url = EmailChangeConfirmation.objects.get_activation_url(
|
||||
key, 'testserver')
|
||||
response = self.client_get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Whoops", response.content.decode('utf8'))
|
||||
|
||||
def test_confirm_email_change_when_time_exceeded(self):
|
||||
# type: () -> None
|
||||
old_email = 'hamlet@zulip.com'
|
||||
new_email = 'hamlet-new@zulip.com'
|
||||
user_profile = get_user_profile_by_email(old_email)
|
||||
obj = EmailChangeStatus.objects.create(new_email=new_email,
|
||||
old_email=old_email,
|
||||
user_profile=user_profile,
|
||||
realm=user_profile.realm)
|
||||
key = generate_key()
|
||||
date_sent = now() - datetime.timedelta(days=2)
|
||||
EmailChangeConfirmation.objects.create(content_object=obj,
|
||||
date_sent=date_sent,
|
||||
confirmation_key=key)
|
||||
url = EmailChangeConfirmation.objects.get_activation_url(key)
|
||||
response = self.client_get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Whoops", response.content.decode('utf8'))
|
||||
|
||||
def test_confirm_email_change(self):
|
||||
# type: () -> None
|
||||
old_email = 'hamlet@zulip.com'
|
||||
new_email = 'hamlet-new@zulip.com'
|
||||
user_profile = get_user_profile_by_email(old_email)
|
||||
obj = EmailChangeStatus.objects.create(new_email=new_email,
|
||||
old_email=old_email,
|
||||
user_profile=user_profile,
|
||||
realm=user_profile.realm)
|
||||
key = generate_key()
|
||||
EmailChangeConfirmation.objects.create(content_object=obj,
|
||||
date_sent=now(),
|
||||
confirmation_key=key)
|
||||
url = EmailChangeConfirmation.objects.get_activation_url(key)
|
||||
response = self.client_get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("This confirms that the email address for your Zulip",
|
||||
response.content.decode('utf8'))
|
||||
user_profile = get_user_profile_by_email(new_email)
|
||||
self.assertTrue(bool(user_profile))
|
||||
obj.refresh_from_db()
|
||||
self.assertEqual(obj.status, 1)
|
||||
|
||||
def test_start_email_change_process(self):
|
||||
# type: () -> None
|
||||
user_profile = get_user_profile_by_email('hamlet@zulip.com')
|
||||
do_start_email_change_process(user_profile, 'hamlet-new@zulip.com')
|
||||
self.assertEqual(EmailChangeStatus.objects.count(), 1)
|
||||
|
||||
def test_end_to_end_flow(self):
|
||||
# type: () -> None
|
||||
data = {'email': 'hamlet-new@zulip.com'}
|
||||
email = 'hamlet@zulip.com'
|
||||
self.login(email)
|
||||
url = '/json/settings/change'
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
result = self.client_post(url, data)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertIn('We have sent you an email', result.content.decode('utf8'))
|
||||
email_message = mail.outbox[0]
|
||||
self.assertEqual(
|
||||
email_message.subject,
|
||||
'[Zulip] Confirm your new email address for Zulip Dev'
|
||||
)
|
||||
body = email_message.body
|
||||
self.assertIn('We received a request to change the email', body)
|
||||
|
||||
activation_url = [s for s in body.split('\n') if s][4]
|
||||
response = self.client_get(activation_url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("This confirms that the email address",
|
||||
response.content.decode('utf8'))
|
||||
|
||||
def test_post_invalid_email(self):
|
||||
# type: () -> None
|
||||
data = {'email': 'hamlet-new'}
|
||||
email = 'hamlet@zulip.com'
|
||||
self.login(email)
|
||||
url = '/json/settings/change'
|
||||
result = self.client_post(url, data)
|
||||
self.assertIn('Invalid address', result.content.decode('utf8'))
|
||||
|
||||
def test_post_same_email(self):
|
||||
# type: () -> None
|
||||
data = {'email': 'hamlet@zulip.com'}
|
||||
email = 'hamlet@zulip.com'
|
||||
self.login(email)
|
||||
url = '/json/settings/change'
|
||||
result = self.client_post(url, data)
|
||||
self.assertEqual('success', result.json()['result'])
|
||||
self.assertEqual('', result.json()['msg'])
|
|
@ -84,6 +84,10 @@ class TemplateTestCase(ZulipTestCase):
|
|||
'confirmation/mituser_confirmation_email_subject.txt',
|
||||
'confirmation/mituser_invite_email_body.txt',
|
||||
'confirmation/mituser_invite_email_subject.txt',
|
||||
'confirmation/emailchangestatus_confirmation_email.subject',
|
||||
'confirmation/emailchangestatus_confirmation_email.html',
|
||||
'confirmation/emailchangestatus_confirmation_email.txt',
|
||||
'confirmation/notify_change_in_email_subject.txt',
|
||||
'corporate/mit.html',
|
||||
'corporate/privacy.html',
|
||||
'corporate/zephyr.html',
|
||||
|
|
|
@ -5,7 +5,11 @@ from typing import Text
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, update_session_auth_hash
|
||||
from django.core.mail import send_mail
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
|
||||
from zerver.decorator import authenticated_json_post_view, has_request_variables, REQ
|
||||
from zerver.lib.actions import do_change_password, \
|
||||
|
@ -17,7 +21,8 @@ from zerver.lib.actions import do_change_password, \
|
|||
do_change_enable_stream_desktop_notifications, do_change_enable_stream_sounds, \
|
||||
do_regenerate_api_key, do_change_avatar_fields, do_change_twenty_four_hour_time, \
|
||||
do_change_left_side_userlist, do_change_default_language, \
|
||||
do_change_pm_content_in_desktop_notifications
|
||||
do_change_pm_content_in_desktop_notifications, validate_email, \
|
||||
do_change_user_email, do_start_email_change_process
|
||||
from zerver.lib.avatar import avatar_url
|
||||
from zerver.lib.i18n import get_available_language_codes
|
||||
from zerver.lib.response import json_success, json_error
|
||||
|
@ -25,7 +30,43 @@ from zerver.lib.upload import upload_avatar_image
|
|||
from zerver.lib.validator import check_bool, check_string
|
||||
from zerver.lib.request import JsonableError
|
||||
from zerver.lib.users import check_change_full_name
|
||||
from zerver.models import UserProfile, Realm, name_changes_disabled
|
||||
from zerver.models import UserProfile, Realm, name_changes_disabled, \
|
||||
EmailChangeStatus
|
||||
from confirmation.models import EmailChangeConfirmation
|
||||
|
||||
def confirm_email_change(request, confirmation_key):
|
||||
# type: (HttpRequest, str) -> HttpResponse
|
||||
confirmation_key = confirmation_key.lower()
|
||||
obj = EmailChangeConfirmation.objects.confirm(confirmation_key)
|
||||
confirmed = False
|
||||
new_email = old_email = None # type: Text
|
||||
if obj:
|
||||
confirmed = True
|
||||
assert isinstance(obj, EmailChangeStatus)
|
||||
new_email = obj.new_email
|
||||
old_email = obj.old_email
|
||||
|
||||
do_change_user_email(obj.user_profile, obj.new_email)
|
||||
|
||||
context = {'support_email': settings.ZULIP_ADMINISTRATOR,
|
||||
'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS,
|
||||
'realm': obj.realm,
|
||||
'new_email': new_email,
|
||||
}
|
||||
subject = render_to_string(
|
||||
'confirmation/notify_change_in_email_subject.txt', context)
|
||||
body = render_to_string(
|
||||
'confirmation/notify_change_in_email_body.txt', context)
|
||||
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [old_email])
|
||||
|
||||
ctx = {
|
||||
'confirmed': confirmed,
|
||||
'support_email': settings.ZULIP_ADMINISTRATOR,
|
||||
'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS,
|
||||
'new_email': new_email,
|
||||
'old_email': old_email,
|
||||
}
|
||||
return render(request, 'confirmation/confirm_email_change.html', context=ctx)
|
||||
|
||||
@has_request_variables
|
||||
def json_change_ui_settings(request, user_profile,
|
||||
|
@ -53,11 +94,12 @@ def json_change_ui_settings(request, user_profile,
|
|||
@has_request_variables
|
||||
def json_change_settings(request, user_profile,
|
||||
full_name=REQ(default=""),
|
||||
email=REQ(default=""),
|
||||
old_password=REQ(default=""),
|
||||
new_password=REQ(default=""),
|
||||
confirm_password=REQ(default="")):
|
||||
# type: (HttpRequest, UserProfile, Text, Text, Text, Text) -> HttpResponse
|
||||
if not (full_name or new_password):
|
||||
# type: (HttpRequest, UserProfile, Text, Text, Text, Text, Text) -> HttpResponse
|
||||
if not (full_name or new_password or email):
|
||||
return json_error(_("No new data supplied"))
|
||||
|
||||
if new_password != "" or confirm_password != "":
|
||||
|
@ -82,6 +124,16 @@ def json_change_settings(request, user_profile,
|
|||
request.session.save()
|
||||
|
||||
result = {}
|
||||
new_email = email.strip()
|
||||
if user_profile.email != email and new_email != '':
|
||||
error, skipped = validate_email(user_profile, new_email)
|
||||
if error or skipped:
|
||||
return json_error(error or skipped)
|
||||
|
||||
do_start_email_change_process(user_profile, new_email)
|
||||
result['account_email'] = _('We have sent you an email on your '
|
||||
'new email address for confirmation.')
|
||||
|
||||
if user_profile.full_name != full_name and full_name.strip() != "":
|
||||
if name_changes_disabled(user_profile.realm):
|
||||
# Failingly silently is fine -- they can't do it through the UI, so
|
||||
|
|
|
@ -99,6 +99,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
|
|||
'TWITTER_CONSUMER_SECRET': '',
|
||||
'TWITTER_ACCESS_TOKEN_KEY': '',
|
||||
'TWITTER_ACCESS_TOKEN_SECRET': '',
|
||||
'EMAIL_CHANGE_CONFIRMATION_DAYS': 1,
|
||||
'EMAIL_GATEWAY_PATTERN': '',
|
||||
'EMAIL_GATEWAY_EXAMPLE': '',
|
||||
'EMAIL_GATEWAY_BOT': None,
|
||||
|
|
|
@ -25,6 +25,7 @@ import zerver.views.zephyr
|
|||
import zerver.views.users
|
||||
import zerver.views.unsubscribe
|
||||
import zerver.views.integrations
|
||||
import zerver.views.user_settings
|
||||
import confirmation.views
|
||||
|
||||
from zerver.lib.rest import rest_dispatch
|
||||
|
@ -103,6 +104,10 @@ i18n_urls = [
|
|||
name='zerver.views.registration.accounts_register'),
|
||||
url(r'^accounts/do_confirm/(?P<confirmation_key>[\w]+)', confirmation.views.confirm, name='confirmation.views.confirm'),
|
||||
|
||||
url(r'^accounts/confirm_new_email/(?P<confirmation_key>[\w]+)',
|
||||
zerver.views.user_settings.confirm_email_change,
|
||||
name='zerver.views.user_settings.confirm_email_change'),
|
||||
|
||||
# Email unsubscription endpoint. Allows for unsubscribing from various types of emails,
|
||||
# including the welcome emails (day 1 & 2), missed PMs, etc.
|
||||
url(r'^accounts/unsubscribe/(?P<type>[\w]+)/(?P<token>[\w]+)',
|
||||
|
|
Loading…
Reference in New Issue