Merge pull request #302 from TheLastProject/feature/custom_domain_random_suffix

Support random suffix for personal domains
This commit is contained in:
Son Nguyen Kim 2020-10-11 18:06:06 +02:00 committed by GitHub
commit 4e45a619cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 31 deletions

View File

@ -57,7 +57,7 @@
</div> </div>
<hr> <hr>
<div>Default Alias name</div> <div>Default Alias Name</div>
<div class="small-text"> <div class="small-text">
This name will be used as the default alias name when you send This name will be used as the default alias name when you send
or reply from an alias, unless overwritten by the alias specific name. or reply from an alias, unless overwritten by the alias specific name.
@ -76,6 +76,32 @@
</form> </form>
</div> </div>
<hr>
<div>Random Prefix Generation</div>
<div class="small-text">
A random prefix can be generated for this domain for usage in the New Alias
feature.
</div>
<div>
<form method="post">
<input type="hidden" name="form-name" value="switch-random-prefix-generation">
<label class="custom-switch cursor mt-2 pl-0"
data-toggle="tooltip"
{% if custom_domain.random_prefix_generation %}
title="Disable random prefix generation"
{% else %}
title="Enable random prefix generation"
{% endif %}
>
<input type="checkbox" class="custom-switch-input"
{{ "checked" if custom_domain.random_prefix_generation else "" }}>
<span class="custom-switch-indicator"></span>
</label>
</form>
</div>
<hr> <hr>
<h3 class="mb-0">Delete Domain</h3> <h3 class="mb-0">Delete Domain</h3>
<div class="small-text mb-3">Please note that this operation is irreversible. <div class="small-text mb-3">Please note that this operation is irreversible.

View File

@ -8,7 +8,6 @@ from app.config import (
CUSTOM_ALIAS_SECRET, CUSTOM_ALIAS_SECRET,
) )
from app.dashboard.base import dashboard_bp from app.dashboard.base import dashboard_bp
from app.email_utils import email_belongs_to_alias_domains
from app.extensions import db from app.extensions import db
from app.log import LOG from app.log import LOG
from app.models import ( from app.models import (
@ -27,15 +26,19 @@ signer = TimestampSigner(CUSTOM_ALIAS_SECRET)
def available_suffixes(user: User) -> [bool, str, str]: def available_suffixes(user: User) -> [bool, str, str]:
"""Return (is_custom_domain, alias-suffix, time-signed alias-suffix)""" """Return (is_custom_domain, alias-suffix, time-signed alias-suffix)"""
user_custom_domains = [cd.domain for cd in user.verified_custom_domains()] user_custom_domains = user.verified_custom_domains()
# List of (is_custom_domain, alias-suffix, time-signed alias-suffix) # List of (is_custom_domain, alias-suffix, time-signed alias-suffix)
suffixes = [] suffixes = []
# put custom domain first # put custom domain first
# for each user domain, generate both the domain and a random suffix version
for alias_domain in user_custom_domains: for alias_domain in user_custom_domains:
suffix = "@" + alias_domain suffix = "@" + alias_domain.domain
suffixes.append((True, suffix, signer.sign(suffix).decode())) suffixes.append((True, suffix, signer.sign(suffix).decode()))
if alias_domain.random_prefix_generation:
suffix = "." + random_word() + "@" + alias_domain.domain
suffixes.append((True, suffix, signer.sign(suffix).decode()))
# then default domain # then default domain
for domain in ALIAS_DOMAINS: for domain in ALIAS_DOMAINS:
@ -193,35 +196,23 @@ def verify_prefix_suffix(user, alias_prefix, alias_suffix) -> bool:
# make sure alias_suffix is either .random_word@simplelogin.co or @my-domain.com # make sure alias_suffix is either .random_word@simplelogin.co or @my-domain.com
alias_suffix = alias_suffix.strip() alias_suffix = alias_suffix.strip()
if alias_suffix.startswith("@"): # alias_domain_prefix is either a .random_word or ""
alias_domain = alias_suffix[1:] alias_domain_prefix, alias_domain = alias_suffix.split("@", 1)
# alias_domain can be either custom_domain or if DISABLE_ALIAS_SUFFIX, one of the default ALIAS_DOMAINS
if DISABLE_ALIAS_SUFFIX: # alias_domain must be either one of user custom domains or built-in domains
if ( if alias_domain not in user_custom_domains and alias_domain not in ALIAS_DOMAINS:
alias_domain not in user_custom_domains LOG.exception("wrong alias suffix %s, user %s", alias_suffix, user)
and alias_domain not in ALIAS_DOMAINS return False
):
LOG.exception("wrong alias suffix %s, user %s", alias_suffix, user) # built-in domain case:
return False # 1) alias_suffix must start with "." and
else: # 2) alias_domain_prefix must come from the word list
if alias_domain not in user_custom_domains: if alias_domain in ALIAS_DOMAINS and alias_domain not in user_custom_domains:
LOG.exception("wrong alias suffix %s, user %s", alias_suffix, user) if not alias_domain_prefix.startswith("."):
return False
else:
if not alias_suffix.startswith("."):
LOG.exception("User %s submits a wrong alias suffix %s", user, alias_suffix) LOG.exception("User %s submits a wrong alias suffix %s", user, alias_suffix)
return False return False
full_alias = alias_prefix + alias_suffix random_word_part = alias_domain_prefix[1:]
if not email_belongs_to_alias_domains(full_alias):
LOG.exception(
"Alias suffix should end with one of the alias domains %s",
user,
alias_suffix,
)
return False
random_word_part = alias_suffix[1 : alias_suffix.find("@")]
if not word_exist(random_word_part): if not word_exist(random_word_part):
LOG.exception( LOG.exception(
"alias suffix %s needs to start with a random word, user %s", "alias suffix %s needs to start with a random word, user %s",
@ -229,5 +220,14 @@ def verify_prefix_suffix(user, alias_prefix, alias_suffix) -> bool:
user, user,
) )
return False return False
else:
if alias_domain not in user_custom_domains:
if not DISABLE_ALIAS_SUFFIX:
LOG.exception("wrong alias suffix %s, user %s", alias_suffix, user)
return False
if alias_domain not in ALIAS_DOMAINS:
LOG.exception("wrong alias suffix %s, user %s", alias_suffix, user)
return False
return True return True

View File

@ -160,6 +160,25 @@ def domain_detail(custom_domain_id):
return redirect( return redirect(
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id) url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
) )
elif request.form.get("form-name") == "switch-random-prefix-generation":
custom_domain.random_prefix_generation = (
not custom_domain.random_prefix_generation
)
db.session.commit()
if custom_domain.random_prefix_generation:
flash(
f"Random prefix generation has been enabled for {custom_domain.domain}",
"success",
)
else:
flash(
f"Random prefix generation has been disabled for {custom_domain.domain}",
"warning",
)
return redirect(
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
)
elif request.form.get("form-name") == "delete": elif request.form.get("form-name") == "delete":
name = custom_domain.domain name = custom_domain.domain
CustomDomain.delete(custom_domain_id) CustomDomain.delete(custom_domain_id)

View File

@ -1449,6 +1449,11 @@ class CustomDomain(db.Model, ModelMixin):
# an alias is created automatically the first time it receives an email # an alias is created automatically the first time it receives an email
catch_all = db.Column(db.Boolean, nullable=False, default=False, server_default="0") catch_all = db.Column(db.Boolean, nullable=False, default=False, server_default="0")
# option to generate random prefix version automatically
random_prefix_generation = db.Column(
db.Boolean, nullable=False, default=False, server_default="0"
)
user = db.relationship(User, foreign_keys=[user_id]) user = db.relationship(User, foreign_keys=[user_id])
@property @property

View File

@ -0,0 +1,29 @@
"""empty message
Revision ID: a90e423c6763
Revises: 1abfc9e14d7e
Create Date: 2020-10-09 22:35:11.359186
"""
import sqlalchemy_utils
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a90e423c6763'
down_revision = '1abfc9e14d7e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('custom_domain', sa.Column('random_prefix_generation', sa.Boolean(), server_default='0', nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('custom_domain', 'random_prefix_generation')
# ### end Alembic commands ###