mirror of
https://github.com/simple-login/app.git
synced 2025-10-06 13:26:51 +08:00
Merge pull request #341 from simple-login/revert-reverse-alias-generation
Revert reverse alias generation
This commit is contained in:
commit
811b33a56a
10 changed files with 124 additions and 15 deletions
|
@ -410,7 +410,7 @@ def create_contact_route(alias_id):
|
||||||
alias_id=alias.id,
|
alias_id=alias.id,
|
||||||
website_email=contact_email,
|
website_email=contact_email,
|
||||||
name=contact_name,
|
name=contact_name,
|
||||||
reply_email=generate_reply_email(contact_email),
|
reply_email=generate_reply_email(contact_email, user),
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.d("create reverse-alias for %s %s", contact_addr, alias)
|
LOG.d("create reverse-alias for %s %s", contact_addr, alias)
|
||||||
|
|
|
@ -43,7 +43,12 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<p>This video can also quickly walk you through the steps:</p>
|
<p>This video can also quickly walk you through the steps:</p>
|
||||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/VsypF-DBaow" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/VsypF-DBaow"
|
||||||
|
frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen
|
||||||
|
style="max-width: 100%"
|
||||||
|
>
|
||||||
|
</iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -305,6 +305,31 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- END Reverse-alias -->
|
<!-- END Reverse-alias -->
|
||||||
|
|
||||||
|
<!-- Sender included in reverse-alias -->
|
||||||
|
<div class="card" id="sender-in-ra">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">Include sender address in reverse-alias</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
By default, the reverse-alias is randomly generated and doesn't contain any information about
|
||||||
|
the sender.<br>
|
||||||
|
|
||||||
|
You can however enable this option to include the sender address in the reverse-alias. <br>
|
||||||
|
|
||||||
|
This can be useful when setting up an email filter and makes the reverse-alias more readable.
|
||||||
|
</div>
|
||||||
|
<form method="post" action="#sender-in-ra">
|
||||||
|
<input type="hidden" name="form-name" value="sender-in-ra">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" id="include-sender-ra" name="enable"
|
||||||
|
{% if current_user.include_sender_in_reverse_alias %} checked {% endif %} class="form-check-input">
|
||||||
|
<label for="include-sender-ra">Include sender address in reverse-alias</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-outline-primary">Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END Reverse-alias -->
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-title">Quarantine</div>
|
<div class="card-title">Quarantine</div>
|
||||||
|
|
|
@ -203,7 +203,7 @@ def alias_contact_manager(alias_id):
|
||||||
alias_id=alias.id,
|
alias_id=alias.id,
|
||||||
website_email=contact_email,
|
website_email=contact_email,
|
||||||
name=contact_name,
|
name=contact_name,
|
||||||
reply_email=generate_reply_email(contact_email),
|
reply_email=generate_reply_email(contact_email, current_user),
|
||||||
)
|
)
|
||||||
|
|
||||||
LOG.d("create reverse-alias for %s", contact_addr)
|
LOG.d("create reverse-alias for %s", contact_addr)
|
||||||
|
|
|
@ -255,6 +255,16 @@ def setting():
|
||||||
flash("Your preference has been updated", "success")
|
flash("Your preference has been updated", "success")
|
||||||
return redirect(url_for("dashboard.setting"))
|
return redirect(url_for("dashboard.setting"))
|
||||||
|
|
||||||
|
elif request.form.get("form-name") == "sender-in-ra":
|
||||||
|
choose = request.form.get("enable")
|
||||||
|
if choose == "on":
|
||||||
|
current_user.include_sender_in_reverse_alias = True
|
||||||
|
else:
|
||||||
|
current_user.include_sender_in_reverse_alias = False
|
||||||
|
db.session.commit()
|
||||||
|
flash("Your preference has been updated", "success")
|
||||||
|
return redirect(url_for("dashboard.setting"))
|
||||||
|
|
||||||
elif request.form.get("form-name") == "export-data":
|
elif request.form.get("form-name") == "export-data":
|
||||||
data = {
|
data = {
|
||||||
"email": current_user.email,
|
"email": current_user.email,
|
||||||
|
|
|
@ -802,7 +802,7 @@ def replace(msg: Message, old, new) -> Message:
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def generate_reply_email(contact_email: str) -> str:
|
def generate_reply_email(contact_email: str, user: User) -> str:
|
||||||
"""
|
"""
|
||||||
generate a reply_email (aka reverse-alias), make sure it isn't used by any contact
|
generate a reply_email (aka reverse-alias), make sure it isn't used by any contact
|
||||||
"""
|
"""
|
||||||
|
@ -811,7 +811,14 @@ def generate_reply_email(contact_email: str) -> str:
|
||||||
# "The maximum total length of a user name or other local-part is 64
|
# "The maximum total length of a user name or other local-part is 64
|
||||||
# octets."
|
# octets."
|
||||||
|
|
||||||
if contact_email:
|
# todo: turns this to False after Dec 20 2020
|
||||||
|
include_sender_in_reverse_alias = True
|
||||||
|
|
||||||
|
# user has chosen an option explicitly
|
||||||
|
if user.include_sender_in_reverse_alias is not None:
|
||||||
|
include_sender_in_reverse_alias = user.include_sender_in_reverse_alias
|
||||||
|
|
||||||
|
if include_sender_in_reverse_alias and contact_email:
|
||||||
# control char: 4 chars (ra+, +)
|
# control char: 4 chars (ra+, +)
|
||||||
# random suffix: max 10 chars
|
# random suffix: max 10 chars
|
||||||
# maximum: 64
|
# maximum: 64
|
||||||
|
@ -825,13 +832,13 @@ def generate_reply_email(contact_email: str) -> str:
|
||||||
|
|
||||||
# not use while to avoid infinite loop
|
# not use while to avoid infinite loop
|
||||||
for _ in range(1000):
|
for _ in range(1000):
|
||||||
if contact_email:
|
if include_sender_in_reverse_alias and contact_email:
|
||||||
random_length = random.randint(5, 10)
|
random_length = random.randint(5, 10)
|
||||||
reply_email = (
|
reply_email = (
|
||||||
f"ra+{contact_email}+{random_string(random_length)}@{EMAIL_DOMAIN}"
|
f"ra+{contact_email}+{random_string(random_length)}@{EMAIL_DOMAIN}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
random_length = random.randint(10, 50)
|
random_length = random.randint(20, 50)
|
||||||
reply_email = f"ra+{random_string(random_length)}@{EMAIL_DOMAIN}"
|
reply_email = f"ra+{random_string(random_length)}@{EMAIL_DOMAIN}"
|
||||||
|
|
||||||
if not Contact.get_by(reply_email=reply_email):
|
if not Contact.get_by(reply_email=reply_email):
|
||||||
|
|
|
@ -272,6 +272,11 @@ class User(db.Model, ModelMixin, UserMixin):
|
||||||
db.ForeignKey("alias.id", ondelete="SET NULL"), nullable=True, default=None
|
db.ForeignKey("alias.id", ondelete="SET NULL"), nullable=True, default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# whether to include the sender address in reverse-alias
|
||||||
|
include_sender_in_reverse_alias = db.Column(
|
||||||
|
db.Boolean, default=False, nullable=True
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, email, name, password=None, **kwargs):
|
def create(cls, email, name, password=None, **kwargs):
|
||||||
user: User = super(User, cls).create(email=email, name=name, **kwargs)
|
user: User = super(User, cls).create(email=email, name=name, **kwargs)
|
||||||
|
|
|
@ -229,7 +229,7 @@ def get_or_create_contact(from_header: str, mail_from: str, alias: Alias) -> Con
|
||||||
name=contact_name,
|
name=contact_name,
|
||||||
mail_from=mail_from,
|
mail_from=mail_from,
|
||||||
from_header=from_header,
|
from_header=from_header,
|
||||||
reply_email=generate_reply_email(contact_email)
|
reply_email=generate_reply_email(contact_email, alias.user)
|
||||||
if is_valid_email(contact_email)
|
if is_valid_email(contact_email)
|
||||||
else NOREPLY,
|
else NOREPLY,
|
||||||
)
|
)
|
||||||
|
@ -292,7 +292,7 @@ def replace_header_when_forward(msg: Message, alias: Alias, header: str):
|
||||||
alias_id=alias.id,
|
alias_id=alias.id,
|
||||||
website_email=contact_email,
|
website_email=contact_email,
|
||||||
name=contact_name,
|
name=contact_name,
|
||||||
reply_email=generate_reply_email(contact_email),
|
reply_email=generate_reply_email(contact_email, alias.user),
|
||||||
is_cc=header.lower() == "cc",
|
is_cc=header.lower() == "cc",
|
||||||
from_header=addr,
|
from_header=addr,
|
||||||
)
|
)
|
||||||
|
|
29
migrations/versions/2020_120619_c0d91ff18f77_.py
Normal file
29
migrations/versions/2020_120619_c0d91ff18f77_.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: c0d91ff18f77
|
||||||
|
Revises: 56c790ec8ab4
|
||||||
|
Create Date: 2020-12-06 19:28:11.733022
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy_utils
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'c0d91ff18f77'
|
||||||
|
down_revision = '56c790ec8ab4'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('include_sender_in_reverse_alias', sa.Boolean(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('users', 'include_sender_in_reverse_alias')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -25,6 +25,7 @@ from app.email_utils import (
|
||||||
)
|
)
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.models import User, CustomDomain
|
from app.models import User, CustomDomain
|
||||||
|
from tests.utils import login
|
||||||
|
|
||||||
|
|
||||||
def test_get_email_domain_part():
|
def test_get_email_domain_part():
|
||||||
|
@ -464,23 +465,50 @@ def test_to_bytes():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_reply_email(flask_client):
|
def test_generate_reply_email(flask_client):
|
||||||
reply_email = generate_reply_email("test@example.org")
|
user = User.create(
|
||||||
|
email="a@b.c",
|
||||||
|
password="password",
|
||||||
|
name="Test User",
|
||||||
|
activated=True,
|
||||||
|
)
|
||||||
|
reply_email = generate_reply_email("test@example.org", user)
|
||||||
# return something like
|
# return something like
|
||||||
# ra+test.at.example.org+gjbnnddll@sl.local
|
# ra+<random>@sl.local
|
||||||
assert reply_email.startswith("ra+test.at.example.org+")
|
|
||||||
assert reply_email.endswith(EMAIL_DOMAIN)
|
assert reply_email.endswith(EMAIL_DOMAIN)
|
||||||
|
|
||||||
reply_email = generate_reply_email("")
|
reply_email = generate_reply_email("", user)
|
||||||
# return something like
|
# return something like
|
||||||
# ra+qdrcxzppngmvtajklnhqvvuyyzgkyityrzjwikk@sl.local
|
# ra+qdrcxzppngmvtajklnhqvvuyyzgkyityrzjwikk@sl.local
|
||||||
assert reply_email.startswith("ra+")
|
assert reply_email.startswith("ra+")
|
||||||
assert reply_email.endswith(EMAIL_DOMAIN)
|
assert reply_email.endswith(EMAIL_DOMAIN)
|
||||||
|
|
||||||
reply_email = generate_reply_email("👌汉字@example.org")
|
|
||||||
|
def test_generate_reply_email_include_sender_in_reverse_alias(flask_client):
|
||||||
|
# user enables include_sender_in_reverse_alias
|
||||||
|
user = User.create(
|
||||||
|
email="a@b.c",
|
||||||
|
password="password",
|
||||||
|
name="Test User",
|
||||||
|
activated=True,
|
||||||
|
include_sender_in_reverse_alias=True,
|
||||||
|
)
|
||||||
|
reply_email = generate_reply_email("test@example.org", user)
|
||||||
|
# return something like
|
||||||
|
# ra+test.at.example.org+gjbnnddll@sl.local
|
||||||
|
assert reply_email.startswith("ra+test.at.example.org+")
|
||||||
|
assert reply_email.endswith(EMAIL_DOMAIN)
|
||||||
|
|
||||||
|
reply_email = generate_reply_email("", user)
|
||||||
|
# return something like
|
||||||
|
# ra+qdrcxzppngmvtajklnhqvvuyyzgkyityrzjwikk@sl.local
|
||||||
|
assert reply_email.startswith("ra+")
|
||||||
|
assert reply_email.endswith(EMAIL_DOMAIN)
|
||||||
|
|
||||||
|
reply_email = generate_reply_email("👌汉字@example.org", user)
|
||||||
assert reply_email.startswith("ra+yizi.at.example.org+")
|
assert reply_email.startswith("ra+yizi.at.example.org+")
|
||||||
|
|
||||||
# make sure reply_email only contain lowercase
|
# make sure reply_email only contain lowercase
|
||||||
reply_email = generate_reply_email("TEST@example.org")
|
reply_email = generate_reply_email("TEST@example.org", user)
|
||||||
assert reply_email.startswith("ra+test.at.example.org")
|
assert reply_email.startswith("ra+test.at.example.org")
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue