mirror of https://github.com/zulip/zulip.git
demo-orgs: Add email and password process for demo organization owners.
Creates process for demo organization owners to add an email address and password to their account. Uses the same flow as changing an email (via user settings) at the beginning, but then sends a different email template to the user for the email confirmation process. We also encourage users to set their full name field in the modal for adding an email in a demo organization. We disable the submit button on the form if either input is empty, email or full name. When the user clicks the 'confirm and set password' button in the email sent to confirm the email address sent via the form, their email is updated via confirm_email_change, but the user is redirected to the reset password page for their account (instead of the page for confirming an email change has happened). Once the user successfully sets a password, then they will be prompted to log in with their newly configured email and password.
This commit is contained in:
parent
2e00ca4197
commit
91b40a45fe
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "zerver/emails/email_base_default.html" %}
|
||||||
|
|
||||||
|
{% block illustration %}
|
||||||
|
<img src="{{ email_images_base_uri }}/email_logo.png" alt=""/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>{% trans %}Hi,{% endtrans %}</p>
|
||||||
|
|
||||||
|
<p>{% trans realm_uri=macros.link_tag(realm_uri), new_email=macros.email_tag(new_email) %}We received a request to add the email address {{ new_email }} to your Zulip demo organization account on {{ realm_uri }}. To confirm this update and set a password for this account, please click below:{% endtrans %}
|
||||||
|
<a class="button" href="{{ activate_url }}">{{_('Confirm and set password') }}</a></p>
|
||||||
|
|
||||||
|
<p>{% trans support_email=macros.email_tag(support_email) %}If you did not request this change, please contact us immediately at {{ support_email }}.{% endtrans %}</p>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1 @@
|
||||||
|
{{ _("Verify your new email address for your demo Zulip organization") }}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% trans -%}
|
||||||
|
Hi,
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
We received a request to add the email address {{ new_email }} to your Zulip demo organization account on {{ realm_uri }}. To confirm this update and set a password for this account, please click below:
|
||||||
|
{%- endtrans %}
|
||||||
|
|
||||||
|
|
||||||
|
{{ activate_url }}
|
||||||
|
|
||||||
|
{% trans -%}
|
||||||
|
If you did not request this change, please contact us immediately at <{{ support_email }}>.
|
||||||
|
{%- endtrans %}
|
|
@ -2,6 +2,7 @@ import $ from "jquery";
|
||||||
|
|
||||||
import render_change_email_modal from "../templates/change_email_modal.hbs";
|
import render_change_email_modal from "../templates/change_email_modal.hbs";
|
||||||
import render_confirm_deactivate_own_user from "../templates/confirm_dialog/confirm_deactivate_own_user.hbs";
|
import render_confirm_deactivate_own_user from "../templates/confirm_dialog/confirm_deactivate_own_user.hbs";
|
||||||
|
import render_demo_organization_add_email_modal from "../templates/demo_organization_add_email_modal.hbs";
|
||||||
import render_dialog_change_password from "../templates/dialog_change_password.hbs";
|
import render_dialog_change_password from "../templates/dialog_change_password.hbs";
|
||||||
import render_settings_api_key_modal from "../templates/settings/api_key_modal.hbs";
|
import render_settings_api_key_modal from "../templates/settings/api_key_modal.hbs";
|
||||||
import render_settings_custom_user_profile_field from "../templates/settings/custom_user_profile_field.hbs";
|
import render_settings_custom_user_profile_field from "../templates/settings/custom_user_profile_field.hbs";
|
||||||
|
@ -743,6 +744,91 @@ export function set_up() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function do_demo_organization_add_email(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const $change_email_error = $("#demo_organization_add_email_modal").find("#dialog_error");
|
||||||
|
const data = {};
|
||||||
|
data.email = $("#demo_organization_add_email").val();
|
||||||
|
data.full_name = $("#demo_organization_update_full_name").val();
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
success_continuation() {
|
||||||
|
if (page_params.development_environment) {
|
||||||
|
const email_msg = render_settings_dev_env_email_access();
|
||||||
|
ui_report.success(
|
||||||
|
email_msg,
|
||||||
|
$("#dev-account-settings-status").expectOne(),
|
||||||
|
4000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dialog_widget.close_modal();
|
||||||
|
},
|
||||||
|
error_continuation() {
|
||||||
|
dialog_widget.hide_dialog_spinner();
|
||||||
|
},
|
||||||
|
$error_msg_element: $change_email_error,
|
||||||
|
success_msg_html: $t_html(
|
||||||
|
{defaultMessage: "Check your email ({email}) to confirm the new address."},
|
||||||
|
{email: data.email},
|
||||||
|
),
|
||||||
|
sticky: true,
|
||||||
|
};
|
||||||
|
settings_ui.do_settings_change(
|
||||||
|
channel.patch,
|
||||||
|
"/json/settings",
|
||||||
|
data,
|
||||||
|
$("#account-settings-status").expectOne(),
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#demo_organization_add_email_button").on("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
function demo_organization_add_email_post_render() {
|
||||||
|
// Disable submit button if either input is an empty string.
|
||||||
|
const $add_email_element = $("#demo_organization_add_email");
|
||||||
|
const $add_name_element = $("#demo_organization_update_full_name");
|
||||||
|
|
||||||
|
const $demo_organization_submit_button = $(
|
||||||
|
"#demo_organization_add_email_modal .dialog_submit_button",
|
||||||
|
);
|
||||||
|
$demo_organization_submit_button.prop("disabled", true);
|
||||||
|
|
||||||
|
$("#demo_organization_add_email_form input").on("input", () => {
|
||||||
|
$demo_organization_submit_button.prop(
|
||||||
|
"disabled",
|
||||||
|
$add_email_element.val().trim() === "" || $add_name_element.val().trim() === "",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
page_params.demo_organization_scheduled_deletion_date &&
|
||||||
|
page_params.is_owner &&
|
||||||
|
page_params.delivery_email === ""
|
||||||
|
) {
|
||||||
|
dialog_widget.launch({
|
||||||
|
html_heading: $t_html({defaultMessage: "Add email"}),
|
||||||
|
html_body: render_demo_organization_add_email_modal({
|
||||||
|
delivery_email: page_params.delivery_email,
|
||||||
|
full_name: page_params.full_name,
|
||||||
|
}),
|
||||||
|
html_submit_button: $t_html({defaultMessage: "Add"}),
|
||||||
|
loading_spinner: true,
|
||||||
|
id: "demo_organization_add_email_modal",
|
||||||
|
form_id: "demo_organization_add_email_form",
|
||||||
|
on_click: do_demo_organization_add_email,
|
||||||
|
on_shown() {
|
||||||
|
ui_util.place_caret_at_end($("#demo_organization_add_email_form input")[0]);
|
||||||
|
},
|
||||||
|
post_render: demo_organization_add_email_post_render,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#profile-settings").on("click", ".custom_user_field .remove_date", (e) => {
|
$("#profile-settings").on("click", ".custom_user_field .remove_date", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<form id="demo_organization_add_email_form" class="new-style">
|
||||||
|
<div class="tip">{{t "If you haven't updated your name, it's a good idea to do so before inviting other users to join you!" }}</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="demo_organization_add_email">{{t "Email" }}</label>
|
||||||
|
<input id="demo_organization_add_email" type="text" name="email" class="modal_text_input" value="{{delivery_email}}" autocomplete="off" spellcheck="false" autofocus="autofocus"/>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="demo_organization_update_full_name">{{t "Full name" }}</label>
|
||||||
|
<input id="demo_organization_update_full_name" name="full_name" type="text" class="modal_text_input" value="{{full_name}}" maxlength="60" />
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -6,6 +6,7 @@
|
||||||
<h3 class="inline-block">{{t "Account" }}</h3>
|
<h3 class="inline-block">{{t "Account" }}</h3>
|
||||||
<div class="alert-notification" id="account-settings-status"></div>
|
<div class="alert-notification" id="account-settings-status"></div>
|
||||||
<form class="grid">
|
<form class="grid">
|
||||||
|
{{#if user_has_email_set}}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="inline-block title">{{t "Email" }}</label>
|
<label class="inline-block title">{{t "Email" }}</label>
|
||||||
<div id="change_email_button_container" class="inline-block {{#unless user_can_change_email}}disabled_setting_tooltip{{/unless}}">
|
<div id="change_email_button_container" class="inline-block {{#unless user_can_change_email}}disabled_setting_tooltip{{/unless}}">
|
||||||
|
@ -15,6 +16,22 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{! Demo organizations before the owner has configured an email address. }}
|
||||||
|
<div class="input-group">
|
||||||
|
<p>
|
||||||
|
{{#tr}}
|
||||||
|
Add your email to <z-link-invite-users-help>invite other users</z-link-invite-users-help>
|
||||||
|
or <z-link-convert-demo-organization-help>convert to a permanent Zulip organization</z-link-convert-demo-organization-help>.
|
||||||
|
{{#*inline "z-link-invite-users-help"}}<a href="/help/invite-new-users" target="_blank" rel="noopener noreferrer">{{> @partial-block}}</a>{{/inline}}
|
||||||
|
{{#*inline "z-link-convert-demo-organization-help"}}<a href="/help/demo-organizations#convert-a-demo-organization-to-a-permanent-organization" target="_blank" rel="noopener noreferrer">{{> @partial-block}}</a>{{/inline}}
|
||||||
|
{{/tr}}
|
||||||
|
</p>
|
||||||
|
<button id="demo_organization_add_email_button" type="button" class="button rounded sea-green">
|
||||||
|
{{t "Add email"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{{#if page_params.two_fa_enabled }}
|
{{#if page_params.two_fa_enabled }}
|
||||||
|
|
|
@ -151,8 +151,20 @@ def do_start_email_change_process(user_profile: UserProfile, new_email: str) ->
|
||||||
activate_url=activation_url,
|
activate_url=activation_url,
|
||||||
)
|
)
|
||||||
language = user_profile.default_language
|
language = user_profile.default_language
|
||||||
|
|
||||||
|
email_template = "zerver/emails/confirm_new_email"
|
||||||
|
|
||||||
|
if old_email == "":
|
||||||
|
# The assertions here are to help document the only circumstance under which
|
||||||
|
# this condition should be possible.
|
||||||
|
assert (
|
||||||
|
user_profile.realm.demo_organization_scheduled_deletion_date is not None
|
||||||
|
and user_profile.is_realm_owner
|
||||||
|
)
|
||||||
|
email_template = "zerver/emails/confirm_demo_organization_email"
|
||||||
|
|
||||||
send_email(
|
send_email(
|
||||||
"zerver/emails/confirm_new_email",
|
template_prefix=email_template,
|
||||||
to_emails=[new_email],
|
to_emails=[new_email],
|
||||||
from_name=FromAddress.security_email_from_name(language=language),
|
from_name=FromAddress.security_email_from_name(language=language),
|
||||||
from_address=FromAddress.tokenized_no_reply_address(),
|
from_address=FromAddress.tokenized_no_reply_address(),
|
||||||
|
|
|
@ -296,3 +296,48 @@ class EmailChangeTestCase(ZulipTestCase):
|
||||||
with self.assertRaises(UserProfile.DoesNotExist):
|
with self.assertRaises(UserProfile.DoesNotExist):
|
||||||
get_user_by_delivery_email(old_email, user_profile.realm)
|
get_user_by_delivery_email(old_email, user_profile.realm)
|
||||||
self.assertEqual(get_user_by_delivery_email(new_email, user_profile.realm), user_profile)
|
self.assertEqual(get_user_by_delivery_email(new_email, user_profile.realm), user_profile)
|
||||||
|
|
||||||
|
def test_configure_demo_organization_owner_email(self) -> None:
|
||||||
|
desdemona = self.example_user("desdemona")
|
||||||
|
desdemona.realm.demo_organization_scheduled_deletion_date = now() + datetime.timedelta(
|
||||||
|
days=30
|
||||||
|
)
|
||||||
|
desdemona.realm.save()
|
||||||
|
assert desdemona.realm.demo_organization_scheduled_deletion_date is not None
|
||||||
|
|
||||||
|
self.login("desdemona")
|
||||||
|
desdemona.delivery_email = ""
|
||||||
|
desdemona.save()
|
||||||
|
self.assertEqual(desdemona.delivery_email, "")
|
||||||
|
|
||||||
|
data = {"email": "desdemona-new@zulip.com"}
|
||||||
|
url = "/json/settings"
|
||||||
|
self.assert_length(mail.outbox, 0)
|
||||||
|
result = self.client_patch(url, data)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
self.assert_length(mail.outbox, 1)
|
||||||
|
|
||||||
|
email_message = mail.outbox[0]
|
||||||
|
self.assertEqual(
|
||||||
|
email_message.subject,
|
||||||
|
"Verify your new email address for your demo Zulip organization",
|
||||||
|
)
|
||||||
|
body = email_message.body
|
||||||
|
self.assertIn(
|
||||||
|
"We received a request to add the email address",
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.assertEqual(self.email_envelope_from(email_message), settings.NOREPLY_EMAIL_ADDRESS)
|
||||||
|
self.assertRegex(
|
||||||
|
self.email_display_from(email_message),
|
||||||
|
rf"^Zulip Account Security <{self.TOKENIZED_NOREPLY_REGEX}>\Z",
|
||||||
|
)
|
||||||
|
self.assertEqual(email_message.extra_headers["List-Id"], "Zulip Dev <zulip.testserver>")
|
||||||
|
|
||||||
|
confirmation_url = [s for s in body.split("\n") if s][2]
|
||||||
|
response = self.client_get(confirmation_url, follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assert_in_success_response(["Set a new password"], response)
|
||||||
|
|
||||||
|
user_profile = get_user_profile_by_id(desdemona.id)
|
||||||
|
self.assertEqual(user_profile.delivery_email, "desdemona-new@zulip.com")
|
||||||
|
|
|
@ -3,9 +3,10 @@ from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate, update_session_auth_hash
|
from django.contrib.auth import authenticate, update_session_auth_hash
|
||||||
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.files.uploadedfile import UploadedFile
|
from django.core.files.uploadedfile import UploadedFile
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import SafeString
|
from django.utils.safestring import SafeString
|
||||||
|
@ -28,6 +29,7 @@ from zerver.actions.user_settings import (
|
||||||
do_start_email_change_process,
|
do_start_email_change_process,
|
||||||
)
|
)
|
||||||
from zerver.decorator import human_users_only
|
from zerver.decorator import human_users_only
|
||||||
|
from zerver.forms import generate_password_reset_url
|
||||||
from zerver.lib.avatar import avatar_url
|
from zerver.lib.avatar import avatar_url
|
||||||
from zerver.lib.email_validation import (
|
from zerver.lib.email_validation import (
|
||||||
get_realm_email_validator,
|
get_realm_email_validator,
|
||||||
|
@ -88,6 +90,20 @@ def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpRes
|
||||||
|
|
||||||
context = {"realm_name": user_profile.realm.name, "new_email": new_email}
|
context = {"realm_name": user_profile.realm.name, "new_email": new_email}
|
||||||
language = user_profile.default_language
|
language = user_profile.default_language
|
||||||
|
|
||||||
|
if old_email == "":
|
||||||
|
# The assertions here are to help document the only circumstance under which
|
||||||
|
# this condition should be possible.
|
||||||
|
assert (
|
||||||
|
user_profile.realm.demo_organization_scheduled_deletion_date is not None
|
||||||
|
and user_profile.is_realm_owner
|
||||||
|
)
|
||||||
|
# Because demo organizations are created without setting an email and password
|
||||||
|
# we want to redirect to setting a password after configuring and confirming
|
||||||
|
# an email for the owner's account.
|
||||||
|
reset_password_url = generate_password_reset_url(user_profile, default_token_generator)
|
||||||
|
return HttpResponseRedirect(reset_password_url)
|
||||||
|
|
||||||
send_email(
|
send_email(
|
||||||
"zerver/emails/notify_change_in_email",
|
"zerver/emails/notify_change_in_email",
|
||||||
to_emails=[old_email],
|
to_emails=[old_email],
|
||||||
|
|
Loading…
Reference in New Issue