mirror of
https://github.com/simple-login/app.git
synced 2024-09-20 15:05:59 +08:00
Implement alias generator schemes
This commit is contained in:
parent
1848e5a944
commit
e2e9cc6b5d
|
@ -24,7 +24,7 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 offset-lg-6 pr-0 mt-1">
|
||||
<div class="col-lg-4 offset-lg-5 pr-0 mt-1">
|
||||
<div class="btn-group float-right" role="group">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create-custom-email">
|
||||
|
@ -33,13 +33,34 @@
|
|||
class="btn btn-primary mr-2">New Email Alias
|
||||
</button>
|
||||
</form>
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<button data-toggle="tooltip"
|
||||
title="Create a totally random alias"
|
||||
class="btn btn-success">Random Alias
|
||||
</button>
|
||||
</form>
|
||||
<div class="btn-group" role="group">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<button data-toggle="tooltip"
|
||||
title="Create a totally random alias"
|
||||
class="btn btn-success">Random Alias
|
||||
</button>
|
||||
</form>
|
||||
<button id="btnGroupDrop1" type="button" class="btn btn-success dropdown-toggle"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="btnGroupDrop1">
|
||||
<div class="">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<input type="hidden" name="generator_scheme" value="{{ AliasGeneratorEnum.word.value }}">
|
||||
<button class="dropdown-item">By random words</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<input type="hidden" name="generator_scheme" value="{{ AliasGeneratorEnum.uuid.value }}">
|
||||
<button class="dropdown-item">By UUID</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -65,6 +65,18 @@
|
|||
<input type="hidden" name="form-name" value="change-password">
|
||||
<button class="btn btn-outline-primary">Change password</button>
|
||||
</form>
|
||||
<hr>
|
||||
|
||||
<h3 class="mb-0">Random Alias</h3>
|
||||
<div class="small-text mb-3">Choose how to create your email alias by default</div>
|
||||
<form method="post" class="form-inline">
|
||||
<input type="hidden" name="form-name" value="change-alias-generator">
|
||||
<select class="custom-select mr-sm-2" name="alias-generator-scheme" id="alias-generator-scheme">
|
||||
<option value="{{ AliasGeneratorEnum.word.value }}" {% if current_user.alias_generator == AliasGeneratorEnum.word.value %} selected {% endif %} >Based on Random {{ AliasGeneratorEnum.word.name.capitalize() }}</option>
|
||||
<option value="{{ AliasGeneratorEnum.uuid.value }}" {% if current_user.alias_generator == AliasGeneratorEnum.uuid.value %} selected {% endif %} >Based on {{ AliasGeneratorEnum.uuid.name.upper() }}</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-primary">Update Preference</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
<h3 class="mb-0">Export Data</h3>
|
||||
|
|
|
@ -9,7 +9,7 @@ from app.config import HIGHLIGHT_GEN_EMAIL_ID
|
|||
from app.dashboard.base import dashboard_bp
|
||||
from app.extensions import db
|
||||
from app.log import LOG
|
||||
from app.models import GenEmail, ClientUser, ForwardEmail, ForwardEmailLog, DeletedAlias
|
||||
from app.models import GenEmail, ClientUser, ForwardEmail, ForwardEmailLog, DeletedAlias, AliasGeneratorEnum
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -57,7 +57,10 @@ def index():
|
|||
|
||||
elif request.form.get("form-name") == "create-random-email":
|
||||
if current_user.can_create_new_alias():
|
||||
gen_email = GenEmail.create_new_random(user_id=current_user.id)
|
||||
scheme = int(request.form.get("generator_scheme") or current_user.alias_generator)
|
||||
if not scheme or not AliasGeneratorEnum.has_value(scheme):
|
||||
scheme = current_user.alias_generator
|
||||
gen_email = GenEmail.create_new_random(user_id=current_user.id, scheme=scheme)
|
||||
db.session.commit()
|
||||
|
||||
LOG.d("generate new email %s for user %s", gen_email, current_user)
|
||||
|
@ -99,9 +102,9 @@ def index():
|
|||
|
||||
client_users = (
|
||||
ClientUser.filter_by(user_id=current_user.id)
|
||||
.options(joinedload(ClientUser.client))
|
||||
.options(joinedload(ClientUser.gen_email))
|
||||
.all()
|
||||
.options(joinedload(ClientUser.client))
|
||||
.options(joinedload(ClientUser.gen_email))
|
||||
.all()
|
||||
)
|
||||
|
||||
sorted(client_users, key=lambda cu: cu.client.name)
|
||||
|
@ -112,6 +115,7 @@ def index():
|
|||
aliases=get_alias_info(current_user.id, query, highlight_gen_email_id),
|
||||
highlight_gen_email_id=highlight_gen_email_id,
|
||||
query=query,
|
||||
AliasGeneratorEnum=AliasGeneratorEnum
|
||||
)
|
||||
|
||||
|
||||
|
@ -150,8 +154,8 @@ def get_alias_info(user_id, query=None, highlight_gen_email_id=None) -> [AliasIn
|
|||
# also add alias that has no forward email or log
|
||||
q = (
|
||||
db.session.query(GenEmail)
|
||||
.filter(GenEmail.email.notin_(aliases.keys()))
|
||||
.filter(GenEmail.user_id == user_id)
|
||||
.filter(GenEmail.email.notin_(aliases.keys()))
|
||||
.filter(GenEmail.user_id == user_id)
|
||||
)
|
||||
|
||||
if query:
|
||||
|
|
|
@ -23,7 +23,7 @@ from app.models import (
|
|||
DeletedAlias,
|
||||
CustomDomain,
|
||||
Client,
|
||||
)
|
||||
AliasGeneratorEnum)
|
||||
from app.utils import random_string
|
||||
|
||||
|
||||
|
@ -121,6 +121,13 @@ def setting():
|
|||
logout_user()
|
||||
return redirect(url_for("auth.register"))
|
||||
|
||||
elif request.form.get("form-name") == "change-alias-generator":
|
||||
scheme = int(request.form.get("alias-generator-scheme"))
|
||||
if AliasGeneratorEnum.has_value(scheme):
|
||||
current_user.alias_generator = scheme
|
||||
db.session.commit()
|
||||
flash("Your preference has been updated", "success")
|
||||
|
||||
elif request.form.get("form-name") == "export-data":
|
||||
data = {
|
||||
"email": current_user.email,
|
||||
|
@ -157,6 +164,7 @@ def setting():
|
|||
PlanEnum=PlanEnum,
|
||||
promo_form=promo_form,
|
||||
pending_email=pending_email,
|
||||
AliasGeneratorEnum=AliasGeneratorEnum
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import enum
|
||||
import random
|
||||
import uuid
|
||||
|
||||
import arrow
|
||||
import bcrypt
|
||||
|
@ -83,6 +84,15 @@ class PlanEnum(enum.Enum):
|
|||
yearly = 3
|
||||
|
||||
|
||||
class AliasGeneratorEnum(enum.Enum):
|
||||
word = 1 # aliases are generated based on random words
|
||||
uuid = 2 # aliases are generated based on uuid
|
||||
|
||||
@classmethod
|
||||
def has_value(cls, value: int) -> bool:
|
||||
return value in set(item.value for item in cls)
|
||||
|
||||
|
||||
class User(db.Model, ModelMixin, UserMixin):
|
||||
__tablename__ = "users"
|
||||
email = db.Column(db.String(128), unique=True, nullable=False)
|
||||
|
@ -90,6 +100,7 @@ class User(db.Model, ModelMixin, UserMixin):
|
|||
password = db.Column(db.String(128), nullable=False)
|
||||
name = db.Column(db.String(128), nullable=False)
|
||||
is_admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||
alias_generator = db.Column(db.Integer, nullable=True, default=AliasGeneratorEnum.word.value)
|
||||
|
||||
activated = db.Column(db.Boolean, default=False, nullable=False)
|
||||
|
||||
|
@ -310,8 +321,8 @@ class Client(db.Model, ModelMixin):
|
|||
def last_user_login(self) -> "ClientUser":
|
||||
client_user = (
|
||||
ClientUser.query.filter(ClientUser.client_id == self.id)
|
||||
.order_by(ClientUser.updated_at)
|
||||
.first()
|
||||
.order_by(ClientUser.updated_at)
|
||||
.first()
|
||||
)
|
||||
if client_user:
|
||||
return client_user
|
||||
|
@ -367,20 +378,27 @@ class OauthToken(db.Model, ModelMixin):
|
|||
return self.expired < arrow.now()
|
||||
|
||||
|
||||
def generate_email() -> str:
|
||||
"""generate an email address that does not exist before"""
|
||||
random_email = random_words() + "@" + EMAIL_DOMAIN
|
||||
def generate_email(scheme: int = 1, in_hex: bool = False) -> str:
|
||||
"""generate an email address that does not exist before
|
||||
:param scheme: int, value of AliasGeneratorEnum, indicate how the email is generated
|
||||
:type in_hex: bool, if the generate scheme is uuid, is hex favorable?
|
||||
"""
|
||||
if scheme == AliasGeneratorEnum.uuid.value:
|
||||
name = uuid.uuid4().hex if in_hex else uuid.uuid4().__str__()
|
||||
random_email = name + "@" + EMAIL_DOMAIN
|
||||
else:
|
||||
random_email = random_words() + "@" + EMAIL_DOMAIN
|
||||
|
||||
# check that the client does not exist yet
|
||||
if not GenEmail.get_by(email=random_email) and not DeletedAlias.get_by(
|
||||
email=random_email
|
||||
email=random_email
|
||||
):
|
||||
LOG.debug("generate email %s", random_email)
|
||||
return random_email
|
||||
|
||||
# Rerun the function
|
||||
LOG.warning("email %s already exists, generate a new email", random_email)
|
||||
return generate_email()
|
||||
return generate_email(scheme=scheme, in_hex=in_hex)
|
||||
|
||||
|
||||
class GenEmail(db.Model, ModelMixin):
|
||||
|
@ -413,9 +431,9 @@ class GenEmail(db.Model, ModelMixin):
|
|||
return GenEmail.create(user_id=user_id, email=email)
|
||||
|
||||
@classmethod
|
||||
def create_new_random(cls, user_id):
|
||||
def create_new_random(cls, user_id, scheme: int = 1, in_hex: bool = False):
|
||||
"""create a new random alias"""
|
||||
random_email = generate_email()
|
||||
random_email = generate_email(scheme=scheme, in_hex=in_hex)
|
||||
return GenEmail.create(user_id=user_id, email=random_email)
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -552,8 +570,8 @@ class ForwardEmail(db.Model, ModelMixin):
|
|||
"""return the most recent reply"""
|
||||
return (
|
||||
ForwardEmailLog.query.filter_by(forward_id=self.id, is_reply=True)
|
||||
.order_by(desc(ForwardEmailLog.created_at))
|
||||
.first()
|
||||
.order_by(desc(ForwardEmailLog.created_at))
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
"""alias generator scheme
|
||||
|
||||
Revision ID: 8f53e718c79a
|
||||
Revises: 9e1b06b9df13
|
||||
Create Date: 2019-12-25 21:21:27.573197
|
||||
|
||||
"""
|
||||
import sqlalchemy_utils
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8f53e718c79a'
|
||||
down_revision = '9e1b06b9df13'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('users', sa.Column('alias_generator', sa.Integer(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('users', 'alias_generator')
|
||||
# ### end Alembic commands ###
|
|
@ -1,4 +1,7 @@
|
|||
from uuid import UUID
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
|
||||
from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN
|
||||
from app.extensions import db
|
||||
|
@ -9,6 +12,12 @@ def test_generate_email(flask_client):
|
|||
email = generate_email()
|
||||
assert email.endswith("@" + EMAIL_DOMAIN)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
UUID(email.split("@")[0], version=4)
|
||||
|
||||
email_uuid = generate_email(scheme=2)
|
||||
assert UUID(email_uuid.split("@")[0], version=4)
|
||||
|
||||
|
||||
def test_profile_picture_url(flask_client):
|
||||
user = User.create(
|
||||
|
|
Loading…
Reference in a new issue