Merge pull request #234 from simple-login/random-alias-domain

Random alias domain
This commit is contained in:
Son Nguyen Kim 2020-07-04 23:34:33 +02:00 committed by GitHub
commit 5e464a824c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 169 additions and 39 deletions

View File

@ -150,24 +150,19 @@
<button class="btn btn-outline-primary">Update</button>
</form>
{% if current_user.has_custom_domain() %}
<div class="mt-3 mb-1">Select the domain for random aliases.</div>
<form method="post" action="#random-alias" class="form-inline">
<input type="hidden" name="form-name" value="change-random-alias-default-domain">
<select class="form-control mr-sm-2" name="random-alias-default-domain">
<option value="" {% if not current_user.default_random_alias_domain_id %} selected {% endif %}>
{{ FIRST_ALIAS_DOMAIN }} (SimpleLogin domain)
</option>
{% for domain in current_user.custom_domains() %}
<option value="{{ domain.id }}"
{% if current_user.default_random_alias_domain_id == domain.id %} selected {% endif %} >
{{ domain.domain }} (your domain)
</option>
<div class="mt-3 mb-1">Select the domain for random aliases.</div>
<form method="post" action="#random-alias" class="form-inline">
<input type="hidden" name="form-name" value="change-random-alias-default-domain">
<select class="form-control mr-sm-2" name="random-alias-default-domain">
{% for is_public, domain in current_user.available_domains_for_random_alias() %}
<option value="{{ domain }}"
{% if current_user.default_random_alias_domain() == domain %} selected {% endif %} >
{{ domain }} ({% if is_public %} SimpleLogin domain {% else %} your domain {% endif %})
</option>
{% endfor %}
</select>
<button class="btn btn-outline-primary">Update</button>
</form>
{% endif %}
</select>
<button class="btn btn-outline-primary">Update</button>
</form>
</div>
</div>

View File

@ -31,6 +31,7 @@ from app.models import (
AliasGeneratorEnum,
ManualSubscription,
SenderFormatEnum,
PublicDomain,
)
from app.utils import random_string
@ -162,20 +163,28 @@ def setting():
elif request.form.get("form-name") == "change-random-alias-default-domain":
default_domain = request.form.get("random-alias-default-domain")
if default_domain:
default_domain_id = int(default_domain)
# sanity check
domain = CustomDomain.get(default_domain_id)
if (
not domain
or domain.user_id != current_user.id
or not domain.verified
):
flash(
"Something went wrong, sorry for the inconvenience. Please retry. ",
"error",
)
return redirect(url_for("dashboard.setting"))
current_user.default_random_alias_domain_id = default_domain_id
custom_domain = CustomDomain.get_by(domain=default_domain)
if custom_domain:
# sanity check
if (
custom_domain.user_id != current_user.id
or not custom_domain.verified
):
LOG.error(
"%s cannot use domain %s", current_user, default_domain
)
else:
# make sure only default_random_alias_domain_id or default_random_alias_public_domain_id is set
current_user.default_random_alias_domain_id = custom_domain.id
current_user.default_random_alias_public_domain_id = None
else:
public_domain = PublicDomain.get_by(domain=default_domain)
if public_domain:
# make sure only default_random_alias_domain_id or default_random_alias_public_domain_id is set
current_user.default_random_alias_public_domain_id = (
public_domain.id
)
current_user.default_random_alias_domain_id = None
else:
current_user.default_random_alias_domain_id = None

View File

@ -2,7 +2,7 @@ import enum
import random
import uuid
from email.utils import formataddr
from typing import List
from typing import List, Tuple
import arrow
import bcrypt
@ -166,8 +166,18 @@ class User(db.Model, ModelMixin, UserMixin):
# Fields for WebAuthn
fido_uuid = db.Column(db.String(), nullable=True, unique=True)
# the default domain that's used when user creates a new random alias
# default_random_alias_domain_id XOR default_random_alias_public_domain_id
default_random_alias_domain_id = db.Column(
db.ForeignKey("custom_domain.id"), nullable=True, default=None
db.ForeignKey("custom_domain.id", ondelete="SET NULL"),
nullable=True,
default=None,
)
default_random_alias_public_domain_id = db.Column(
db.ForeignKey("public_domain.id", ondelete="SET NULL"),
nullable=True,
default=None,
)
# some users could have lifetime premium
@ -452,6 +462,49 @@ class User(db.Model, ModelMixin, UserMixin):
def custom_domains(self):
return CustomDomain.filter_by(user_id=self.id, verified=True).all()
def available_domains_for_random_alias(self) -> List[Tuple[bool, str]]:
"""Return available domains for user to create random aliases
Each result record contains:
- whether the domain is public (i.e. belongs to SimpleLogin)
- the domain
"""
res = []
for public_domain in PublicDomain.query.all():
res.append((True, public_domain.domain))
for custom_domain in CustomDomain.filter_by(
user_id=self.id, verified=True
).all():
res.append((False, custom_domain.domain))
return res
def default_random_alias_domain(self) -> str:
"""return the domain used for the random alias"""
if self.default_random_alias_domain_id:
custom_domain = CustomDomain.get(self.default_random_alias_domain_id)
# sanity check
if (
not custom_domain
or not custom_domain.verified
or custom_domain.user_id != self.id
):
LOG.error("Problem with %s default random alias domain", self)
return FIRST_ALIAS_DOMAIN
return custom_domain.domain
if self.default_random_alias_public_domain_id:
public_domain = PublicDomain.get(self.default_random_alias_public_domain_id)
# sanity check
if not public_domain:
LOG.error("Problem with %s public random alias domain", self)
return FIRST_ALIAS_DOMAIN
return public_domain.domain
return FIRST_ALIAS_DOMAIN
def fido_enabled(self) -> bool:
if self.fido_uuid is not None:
return True
@ -824,12 +877,17 @@ class Alias(db.Model, ModelMixin):
note: str = None,
):
"""create a new random alias"""
domain = None
custom_domain = None
if user.default_random_alias_domain_id:
domain = CustomDomain.get(user.default_random_alias_domain_id)
custom_domain = CustomDomain.get(user.default_random_alias_domain_id)
random_email = generate_email(
scheme=scheme, in_hex=in_hex, alias_domain=domain.domain
scheme=scheme, in_hex=in_hex, alias_domain=custom_domain.domain
)
elif user.default_random_alias_public_domain_id:
public_domain = PublicDomain.get(user.default_random_alias_public_domain_id)
random_email = generate_email(
scheme=scheme, in_hex=in_hex, alias_domain=public_domain.domain
)
else:
random_email = generate_email(scheme=scheme, in_hex=in_hex)
@ -841,8 +899,8 @@ class Alias(db.Model, ModelMixin):
note=note,
)
if domain:
alias.custom_domain_id = domain.id
if custom_domain:
alias.custom_domain_id = custom_domain.id
return alias
@ -1610,3 +1668,9 @@ class Notification(db.Model, ModelMixin):
# whether user has marked the notification as read
read = db.Column(db.Boolean, nullable=False, default=False)
class PublicDomain(db.Model, ModelMixin):
"""SimpleLogin domains that all users can use"""
domain = db.Column(db.String(128), unique=True, nullable=False)

View File

@ -1,5 +1,6 @@
"""Initial loading script"""
from app.models import Mailbox, Contact
from app.config import ALIAS_DOMAINS
from app.models import Mailbox, Contact, PublicDomain
from app.log import LOG
from app.extensions import db
from app.pgp_utils import load_public_key
@ -32,8 +33,20 @@ def load_pgp_public_keys():
LOG.d("Finish load_pgp_public_keys")
def add_public_domains():
for alias_domain in ALIAS_DOMAINS:
if PublicDomain.get_by(domain=alias_domain):
LOG.d("%s is already a public domain", alias_domain)
else:
LOG.info("Add %s to public domain", alias_domain)
PublicDomain.create(domain=alias_domain)
db.session.commit()
if __name__ == "__main__":
app = create_app()
with app.app_context():
load_pgp_public_keys()
add_public_domains()

View File

@ -0,0 +1,44 @@
"""empty message
Revision ID: 270d598c51e3
Revises: 7128f87af701
Create Date: 2020-07-04 23:32:25.297082
"""
import sqlalchemy_utils
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '270d598c51e3'
down_revision = '7128f87af701'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('public_domain',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True),
sa.Column('domain', sa.String(length=128), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('domain')
)
op.add_column('users', sa.Column('default_random_alias_public_domain_id', sa.Integer(), nullable=True))
op.drop_constraint('users_default_random_alias_domain_id_fkey', 'users', type_='foreignkey')
op.create_foreign_key(None, 'users', 'custom_domain', ['default_random_alias_domain_id'], ['id'], ondelete='SET NULL')
op.create_foreign_key(None, 'users', 'public_domain', ['default_random_alias_public_domain_id'], ['id'], ondelete='SET NULL')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'users', type_='foreignkey')
op.drop_constraint(None, 'users', type_='foreignkey')
op.create_foreign_key('users_default_random_alias_domain_id_fkey', 'users', 'custom_domain', ['default_random_alias_domain_id'], ['id'])
op.drop_column('users', 'default_random_alias_public_domain_id')
op.drop_table('public_domain')
# ### end Alembic commands ###

View File

@ -64,6 +64,7 @@ from app.models import (
Referral,
AliasMailbox,
Notification,
PublicDomain,
)
from app.monitor.base import monitor_bp
from app.oauth.base import oauth_bp
@ -286,6 +287,10 @@ def fake_data():
)
db.session.commit()
for d in ["d1.localhost", "d2.localhost"]:
PublicDomain.create(domain=d)
db.session.commit()
@login_manager.user_loader
def load_user(user_id):