mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 16:14:02 +00:00
confirmation: Replace RealmCreationKey - use Confirmation instead.
Fixes #20028. There's no reason to have a special `RealmCreationKey` class - the `Confirmation` system already does this job. This is somewhat complicated by the need to write a migration for `RealmCreationKey`->`Confirmation` for pre-existing, valid objects, to avoid breaking realm creation links that haven't been used yet.
This commit is contained in:
committed by
Tim Abbott
parent
072f234269
commit
40b1f6eb4e
@@ -0,0 +1,112 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-04 20:33
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, transaction
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.migrations.state import StateApps
|
||||
from django.db.models import Max
|
||||
|
||||
|
||||
def migrate_realmcreationkey_to_realmcreationstatus(
|
||||
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
|
||||
) -> None:
|
||||
"""
|
||||
This migration is for switching from using a separate RealmCreationKey class
|
||||
for realm creation keys to just using the Confirmation system.
|
||||
|
||||
The aim is to iterate through all the existing RealmCreationKey and create
|
||||
a corresponding Confirmation+RealmCreationStatus.
|
||||
|
||||
For validity of these objects, we only need to worry about RealmCreationKeys
|
||||
expired due to time. This is taken care of by making sure we set expiry_date
|
||||
on the Confirmation we're creating.
|
||||
The way the RealmCreationKey system worked was to .delete() the RealmCreationKey
|
||||
objects when they were used - so those simply no longer exist and we don't need to
|
||||
worry about this case.
|
||||
"""
|
||||
CAN_CREATE_REALM = 11
|
||||
|
||||
Confirmation = apps.get_model("confirmation", "Confirmation")
|
||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||
RealmCreationKey = apps.get_model("confirmation", "RealmCreationKey")
|
||||
RealmCreationStatus = apps.get_model("zerver", "RealmCreationStatus")
|
||||
realm_creation_status_content_type, created = ContentType.objects.get_or_create(
|
||||
model="realmcreationstatus", app_label="zerver"
|
||||
)
|
||||
|
||||
BATCH_SIZE = 10000
|
||||
max_id = RealmCreationKey.objects.aggregate(Max("id"))["id__max"]
|
||||
if max_id is None:
|
||||
# Nothing to do.
|
||||
return
|
||||
|
||||
lower_bound = 1
|
||||
while lower_bound <= max_id + (BATCH_SIZE / 2):
|
||||
upper_bound = lower_bound + BATCH_SIZE - 1
|
||||
creation_keys = RealmCreationKey.objects.filter(id__range=(lower_bound, upper_bound))
|
||||
creation_statuses_to_create = []
|
||||
confirmations_to_create = []
|
||||
for creation_key in creation_keys:
|
||||
# These keys were generated in the same way as keys for Confirmation objects,
|
||||
# so we can copy them over without breaking anything.
|
||||
key = creation_key.creation_key
|
||||
date_created = creation_key.date_created
|
||||
presume_email_valid = creation_key.presume_email_valid
|
||||
|
||||
creation_status = RealmCreationStatus(
|
||||
status=0, date_created=date_created, presume_email_valid=presume_email_valid
|
||||
)
|
||||
confirmation = Confirmation(
|
||||
content_type=realm_creation_status_content_type,
|
||||
type=CAN_CREATE_REALM,
|
||||
confirmation_key=key,
|
||||
date_sent=date_created,
|
||||
expiry_date=date_created
|
||||
+ timedelta(days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS),
|
||||
)
|
||||
|
||||
# To attach the Confirmations to RealmCreationStatus objects we need to set the
|
||||
# confirmation.object_id to their respective ids. But we haven't saved
|
||||
# the RealmCreationStatus objs to the database yet - so we don't have ids.
|
||||
#
|
||||
# After we .bulk_create() them, their .id attributes will be populated.
|
||||
# So for now we just link the RealmCreationStatus to the Confirmation
|
||||
# via a temporary ._object attribute - which we'll use later to set
|
||||
# the .object_id as intended.
|
||||
confirmation._object = creation_status
|
||||
|
||||
creation_statuses_to_create.append(creation_status)
|
||||
confirmations_to_create.append(confirmation)
|
||||
with transaction.atomic():
|
||||
RealmCreationStatus.objects.bulk_create(creation_statuses_to_create)
|
||||
|
||||
# Now the objects in creation_statuses_to_create have had their .id
|
||||
# attrs populated. For every confirmation, confirmation._object
|
||||
# points to its corresponding RealmCreationStatus - so now we can
|
||||
# set the confirmation.object_id values and clear out the temporary
|
||||
# ._object attr.
|
||||
for confirmation in confirmations_to_create:
|
||||
confirmation.object_id = confirmation._object.id
|
||||
delattr(confirmation, "_object")
|
||||
Confirmation.objects.bulk_create(confirmations_to_create)
|
||||
|
||||
lower_bound += BATCH_SIZE
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
atomic = False
|
||||
|
||||
dependencies = [
|
||||
("confirmation", "0015_alter_confirmation_object_id"),
|
||||
("zerver", "0743_realmcreationstatus"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
migrate_realmcreationkey_to_realmcreationstatus,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
elidable=True,
|
||||
),
|
||||
]
|
||||
15
confirmation/migrations/0017_delete_realmcreationkey.py
Normal file
15
confirmation/migrations/0017_delete_realmcreationkey.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-04 19:08
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("confirmation", "0016_realmcreationkey_to_realmcreationstatus"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="RealmCreationKey",
|
||||
),
|
||||
]
|
||||
@@ -5,7 +5,7 @@ import secrets
|
||||
from base64 import b32encode
|
||||
from collections.abc import Mapping
|
||||
from datetime import timedelta
|
||||
from typing import Optional, TypeAlias, Union, cast
|
||||
from typing import TypeAlias, Union, cast
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
@@ -30,6 +30,7 @@ from zerver.models import (
|
||||
RealmReactivationStatus,
|
||||
UserProfile,
|
||||
)
|
||||
from zerver.models.prereg_users import RealmCreationStatus
|
||||
|
||||
if settings.ZILENCER_ENABLED:
|
||||
from zilencer.models import (
|
||||
@@ -70,6 +71,7 @@ NoZilencerConfirmationObjT: TypeAlias = (
|
||||
| EmailChangeStatus
|
||||
| UserProfile
|
||||
| RealmReactivationStatus
|
||||
| RealmCreationStatus
|
||||
)
|
||||
ZilencerConfirmationObjT: TypeAlias = Union[
|
||||
NoZilencerConfirmationObjT,
|
||||
@@ -153,7 +155,7 @@ def create_confirmation_object(
|
||||
realm = None
|
||||
else:
|
||||
obj = cast(NoZilencerConfirmationObjT, obj)
|
||||
assert not isinstance(obj, PreregistrationRealm)
|
||||
assert not isinstance(obj, PreregistrationRealm | RealmCreationStatus)
|
||||
realm = obj.realm
|
||||
|
||||
current_time = timezone_now()
|
||||
@@ -185,15 +187,17 @@ def create_confirmation_link(
|
||||
url_args: Mapping[str, str] = {},
|
||||
no_associated_realm_object: bool = False,
|
||||
) -> str:
|
||||
return confirmation_url_for(
|
||||
create_confirmation_object(
|
||||
obj,
|
||||
confirmation_type,
|
||||
validity_in_minutes=validity_in_minutes,
|
||||
no_associated_realm_object=no_associated_realm_object,
|
||||
),
|
||||
conf = create_confirmation_object(
|
||||
obj,
|
||||
confirmation_type,
|
||||
validity_in_minutes=validity_in_minutes,
|
||||
no_associated_realm_object=no_associated_realm_object,
|
||||
)
|
||||
result = confirmation_url_for(
|
||||
conf,
|
||||
url_args=url_args,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def confirmation_url_for(confirmation_obj: "Confirmation", url_args: Mapping[str, str] = {}) -> str:
|
||||
@@ -236,6 +240,7 @@ class Confirmation(models.Model):
|
||||
REALM_REACTIVATION = 8
|
||||
REMOTE_SERVER_BILLING_LEGACY_LOGIN = 9
|
||||
REMOTE_REALM_BILLING_LEGACY_LOGIN = 10
|
||||
CAN_CREATE_REALM = 11
|
||||
type = models.PositiveSmallIntegerField()
|
||||
|
||||
class Meta:
|
||||
@@ -272,6 +277,9 @@ _properties = {
|
||||
Confirmation.MULTIUSE_INVITE: ConfirmationType(
|
||||
"join", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
|
||||
),
|
||||
Confirmation.CAN_CREATE_REALM: ConfirmationType(
|
||||
"create_realm", validity_in_days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS
|
||||
),
|
||||
Confirmation.NEW_REALM_USER_REGISTRATION: ConfirmationType("get_prereg_key_and_redirect"),
|
||||
Confirmation.REALM_REACTIVATION: ConfirmationType("realm_reactivation_get"),
|
||||
}
|
||||
@@ -294,47 +302,7 @@ def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> st
|
||||
)
|
||||
|
||||
|
||||
# Functions related to links generated by the generate_realm_creation_link.py
|
||||
# management command.
|
||||
# Note that being validated here will just allow the user to access the create_realm
|
||||
# form, where they will enter their email and go through the regular
|
||||
# Confirmation.NEW_REALM_USER_REGISTRATION pathway.
|
||||
# Arguably RealmCreationKey should just be another ConfirmationObjT and we should
|
||||
# add another Confirmation.type for this; it's this way for historical reasons.
|
||||
|
||||
|
||||
def validate_key(creation_key: str | None) -> Optional["RealmCreationKey"]:
|
||||
"""Get the record for this key, raising InvalidCreationKey if non-None but invalid."""
|
||||
if creation_key is None:
|
||||
return None
|
||||
try:
|
||||
key_record = RealmCreationKey.objects.get(creation_key=creation_key)
|
||||
except RealmCreationKey.DoesNotExist:
|
||||
raise RealmCreationKey.InvalidError
|
||||
time_elapsed = timezone_now() - key_record.date_created
|
||||
if time_elapsed.total_seconds() > settings.REALM_CREATION_LINK_VALIDITY_DAYS * 24 * 3600:
|
||||
raise RealmCreationKey.InvalidError
|
||||
return key_record
|
||||
|
||||
|
||||
def generate_realm_creation_url(by_admin: bool = False) -> str:
|
||||
key = generate_key()
|
||||
RealmCreationKey.objects.create(
|
||||
creation_key=key, date_created=timezone_now(), presume_email_valid=by_admin
|
||||
)
|
||||
return urljoin(
|
||||
settings.ROOT_DOMAIN_URI,
|
||||
reverse("create_realm", kwargs={"creation_key": key}),
|
||||
)
|
||||
from zerver.views.registration import prepare_realm_creation_url
|
||||
|
||||
|
||||
class RealmCreationKey(models.Model):
|
||||
creation_key = models.CharField("activation key", db_index=True, max_length=40)
|
||||
date_created = models.DateTimeField("created", default=timezone_now)
|
||||
|
||||
# True just if we should presume the email address the user enters
|
||||
# is theirs, and skip sending mail to it to confirm that.
|
||||
presume_email_valid = models.BooleanField(default=False)
|
||||
|
||||
class InvalidError(Exception):
|
||||
pass
|
||||
return prepare_realm_creation_url(presume_email_valid=by_admin)
|
||||
|
||||
@@ -217,6 +217,8 @@ _Released 2025-07-29_
|
||||
preserve and update your `postfix` configuration.
|
||||
- The `SOCIAL_AUTH_SYNC_CUSTOM_ATTRS_DICT` setting has been removed.
|
||||
It was deprecated in favor of `SOCIAL_AUTH_SYNC_ATTRS_DICT` in 10.0.
|
||||
- The obscure `REALM_CREATION_LINK_VALIDITY_DAYS` setting was renamed to
|
||||
`CAN_CREATE_REALM_LINK_VALIDITY_DAYS`.
|
||||
|
||||
## Zulip Server 10.x series
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ The above command will output a URL which can be used for creating a
|
||||
new realm and an administrator user for that realm. The link expires
|
||||
after the creation of the realm. The link also expires if not used
|
||||
within 7 days. The expiration period can be changed by modifying
|
||||
`REALM_CREATION_LINK_VALIDITY_DAYS` in settings.py.
|
||||
`CAN_CREATE_REALM_LINK_VALIDITY_DAYS` in settings.py.
|
||||
|
||||
## Subdomains
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ ALL_ZULIP_TABLES = {
|
||||
"zerver_realm",
|
||||
"zerver_realmauditlog",
|
||||
"zerver_realmauthenticationmethod",
|
||||
"zerver_realmcreationstatus",
|
||||
"zerver_realmdomain",
|
||||
"zerver_realmemoji",
|
||||
"zerver_realmexport",
|
||||
@@ -230,6 +231,7 @@ NON_EXPORTED_TABLES = {
|
||||
"zerver_preregistrationuser_streams",
|
||||
"zerver_preregistrationuser_groups",
|
||||
"zerver_realmreactivationstatus",
|
||||
"zerver_realmcreationstatus",
|
||||
# Missed message addresses are low value to export since
|
||||
# missed-message email addresses include the server's hostname and
|
||||
# expire after a few days.
|
||||
|
||||
@@ -14,7 +14,7 @@ class Command(ZulipBaseCommand):
|
||||
Outputs a randomly generated, 1-time-use link for Organization creation.
|
||||
Whoever visits the link can create a new organization on this server, regardless of whether
|
||||
settings.OPEN_REALM_CREATION is enabled. The link would expire automatically after
|
||||
settings.REALM_CREATION_LINK_VALIDITY_DAYS.
|
||||
settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS.
|
||||
|
||||
Usage: ./manage.py generate_realm_creation_link """
|
||||
|
||||
|
||||
27
zerver/migrations/0747_realmcreationstatus.py
Normal file
27
zerver/migrations/0747_realmcreationstatus.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-04 19:08
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0746_alter_channelfolder_unique_together_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="RealmCreationStatus",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||
),
|
||||
),
|
||||
("status", models.IntegerField(default=0)),
|
||||
("date_created", models.DateTimeField(default=django.utils.timezone.now)),
|
||||
("presume_email_valid", models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -170,3 +170,14 @@ class RealmReactivationStatus(models.Model):
|
||||
status = models.IntegerField(default=0)
|
||||
|
||||
realm = models.ForeignKey(Realm, on_delete=CASCADE)
|
||||
|
||||
|
||||
class RealmCreationStatus(models.Model):
|
||||
# status: whether an object has been confirmed.
|
||||
# if confirmed, set to confirmation.settings.STATUS_USED
|
||||
status = models.IntegerField(default=0)
|
||||
date_created = models.DateTimeField(default=timezone_now)
|
||||
|
||||
# True just if we should presume the email address the user enters
|
||||
# is theirs, and skip sending mail to it to confirm that.
|
||||
presume_email_valid = models.BooleanField(default=False)
|
||||
|
||||
@@ -13,7 +13,7 @@ from django.core.management.base import CommandError
|
||||
from django.test import override_settings
|
||||
from typing_extensions import override
|
||||
|
||||
from confirmation.models import RealmCreationKey, generate_realm_creation_url
|
||||
from confirmation.models import Confirmation, generate_realm_creation_url
|
||||
from zerver.actions.create_user import do_create_user
|
||||
from zerver.actions.user_settings import do_change_user_setting
|
||||
from zerver.lib.management import ZulipBaseCommand, check_config
|
||||
@@ -360,11 +360,13 @@ class TestGenerateRealmCreationLink(ZulipTestCase):
|
||||
@override_settings(OPEN_REALM_CREATION=False)
|
||||
def test_realm_creation_with_expired_link(self) -> None:
|
||||
generated_link = generate_realm_creation_url(by_admin=True)
|
||||
key = generated_link[-24:]
|
||||
# Manually expire the link by changing the date of creation
|
||||
obj = RealmCreationKey.objects.get(creation_key=key)
|
||||
obj.date_created -= timedelta(days=settings.REALM_CREATION_LINK_VALIDITY_DAYS + 1)
|
||||
obj.save()
|
||||
key = generated_link.split("/")[-1]
|
||||
# Manually expire the link by changing the date of expiry.
|
||||
confirmation = Confirmation.objects.get(confirmation_key=key)
|
||||
assert confirmation.expiry_date is not None
|
||||
|
||||
confirmation.expiry_date -= timedelta(days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS + 1)
|
||||
confirmation.save()
|
||||
|
||||
result = self.client_get(generated_link)
|
||||
self.assert_in_success_response(["Organization creation link expired or invalid"], result)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from contextlib import suppress
|
||||
from typing import Annotated, Any
|
||||
from typing import Annotated, Any, cast
|
||||
from urllib.parse import urlencode, urljoin
|
||||
|
||||
import orjson
|
||||
@@ -27,11 +27,9 @@ from confirmation import settings as confirmation_settings
|
||||
from confirmation.models import (
|
||||
Confirmation,
|
||||
ConfirmationKeyError,
|
||||
RealmCreationKey,
|
||||
create_confirmation_link,
|
||||
get_object_from_key,
|
||||
render_confirmation_key_error,
|
||||
validate_key,
|
||||
)
|
||||
from zerver.actions.create_realm import do_create_realm
|
||||
from zerver.actions.create_user import do_activate_mirror_dummy_user, do_create_user
|
||||
@@ -101,6 +99,7 @@ from zerver.models import (
|
||||
UserProfile,
|
||||
)
|
||||
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
||||
from zerver.models.prereg_users import RealmCreationStatus
|
||||
from zerver.models.realm_audit_logs import AuditLogEventType, RealmAuditLog
|
||||
from zerver.models.realms import (
|
||||
DisposableEmailError,
|
||||
@@ -1006,6 +1005,19 @@ def prepare_realm_activation_url(
|
||||
return activation_url
|
||||
|
||||
|
||||
def prepare_realm_creation_url(
|
||||
presume_email_valid: bool = False,
|
||||
) -> str:
|
||||
realm_creation_status = RealmCreationStatus.objects.create(
|
||||
presume_email_valid=presume_email_valid
|
||||
)
|
||||
confirmation_url = create_confirmation_link(
|
||||
realm_creation_status, Confirmation.CAN_CREATE_REALM, no_associated_realm_object=True
|
||||
)
|
||||
|
||||
return confirmation_url
|
||||
|
||||
|
||||
def send_confirm_registration_email(
|
||||
email: str,
|
||||
activation_url: str,
|
||||
@@ -1246,15 +1258,9 @@ def realm_import_post_process(
|
||||
|
||||
|
||||
@add_google_analytics
|
||||
def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpResponse:
|
||||
try:
|
||||
key_record = validate_key(creation_key)
|
||||
except RealmCreationKey.InvalidError:
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"zerver/portico_error_pages/realm_creation_link_invalid.html",
|
||||
)
|
||||
if key_record is None:
|
||||
def create_realm(request: HttpRequest, confirmation_key: str | None = None) -> HttpResponse:
|
||||
if confirmation_key is None:
|
||||
realm_creation_obj: RealmCreationStatus | None = None
|
||||
if not settings.OPEN_REALM_CREATION:
|
||||
return TemplateResponse(
|
||||
request,
|
||||
@@ -1265,6 +1271,19 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR
|
||||
request,
|
||||
"zerver/portico_error_pages/realm_creation_disabled.html",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
realm_creation_obj = cast(
|
||||
RealmCreationStatus,
|
||||
get_object_from_key(
|
||||
confirmation_key, [Confirmation.CAN_CREATE_REALM], mark_object_used=False
|
||||
),
|
||||
)
|
||||
except ConfirmationKeyError:
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"zerver/portico_error_pages/realm_creation_link_invalid.html",
|
||||
)
|
||||
|
||||
# When settings.OPEN_REALM_CREATION is enabled, anyone can create a new realm,
|
||||
# with a few restrictions on their email address.
|
||||
@@ -1300,12 +1319,13 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR
|
||||
realm_default_language,
|
||||
import_from,
|
||||
)
|
||||
if key_record is not None and key_record.presume_email_valid:
|
||||
if realm_creation_obj is not None and realm_creation_obj.presume_email_valid:
|
||||
# The user has a token created from the server command line;
|
||||
# skip confirming the email is theirs, taking their word for it.
|
||||
# This is essential on first install if the admin hasn't stopped
|
||||
# to configure outbound email up front, or it isn't working yet.
|
||||
key_record.delete()
|
||||
realm_creation_obj.status = getattr(settings, "STATUS_USED", 1)
|
||||
realm_creation_obj.save(update_fields=["status"])
|
||||
return HttpResponseRedirect(activation_url)
|
||||
|
||||
try:
|
||||
@@ -1322,8 +1342,10 @@ def create_realm(request: HttpRequest, creation_key: str | None = None) -> HttpR
|
||||
return render(request, "500.html", status=500)
|
||||
return config_error(request, "smtp")
|
||||
|
||||
if key_record is not None:
|
||||
key_record.delete()
|
||||
if realm_creation_obj is not None:
|
||||
realm_creation_obj.status = getattr(settings, "STATUS_USED", 1)
|
||||
realm_creation_obj.save(update_fields=["status"])
|
||||
|
||||
url = reverse(
|
||||
"new_realm_send_confirm",
|
||||
query={
|
||||
|
||||
@@ -516,7 +516,7 @@ PERSONAL_ZMIRROR_SERVER: str | None = None
|
||||
# When security-relevant links in emails expire.
|
||||
CONFIRMATION_LINK_DEFAULT_VALIDITY_DAYS = 1
|
||||
INVITATION_LINK_VALIDITY_DAYS = 10
|
||||
REALM_CREATION_LINK_VALIDITY_DAYS = 7
|
||||
CAN_CREATE_REALM_LINK_VALIDITY_DAYS = 7
|
||||
|
||||
# Version number for ToS. Change this if you want to force every
|
||||
# user to click through to re-accept terms of service before using
|
||||
|
||||
@@ -698,7 +698,7 @@ i18n_urls = [
|
||||
# Realm creation
|
||||
path("json/antispam_challenge", get_challenge),
|
||||
path("new/", create_realm),
|
||||
path("new/<creation_key>", create_realm, name="create_realm"),
|
||||
path("new/<confirmation_key>", create_realm, name="create_realm"),
|
||||
# Realm reactivation
|
||||
path("reactivate/", realm_reactivation, name="realm_reactivation"),
|
||||
path("reactivate/<confirmation_key>", realm_reactivation_get, name="realm_reactivation_get"),
|
||||
|
||||
Reference in New Issue
Block a user