From f662adf237c37b64b2504a7517966b53dbca61fb Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 13:39:52 +0700 Subject: [PATCH 01/24] Add User.full_mailbox col --- app/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/models.py b/app/models.py index f6f92a3b..344d23e8 100644 --- a/app/models.py +++ b/app/models.py @@ -137,6 +137,14 @@ class User(db.Model, ModelMixin, UserMixin): db.Boolean, default=False, nullable=False, server_default="0" ) + # only use mailbox instead of default to user email + # this requires a migration before to: + # 1. create default mailbox for the user email address + # 2. assign existing aliases to this default mailbox + full_mailbox = db.Column( + db.Boolean, default=False, nullable=False, server_default="0" + ) + profile_picture = db.relationship(File) @classmethod From 70802bff1752dea3eb5a12adce8b186a71e4d020 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 13:41:27 +0700 Subject: [PATCH 02/24] Create User.mailboxes() --- app/dashboard/views/custom_alias.py | 4 +--- app/models.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/dashboard/views/custom_alias.py b/app/dashboard/views/custom_alias.py index e0f7e7da..cdea83fc 100644 --- a/app/dashboard/views/custom_alias.py +++ b/app/dashboard/views/custom_alias.py @@ -43,9 +43,7 @@ def custom_alias(): ) ) - mailboxes = [current_user.email] - for mailbox in Mailbox.query.filter_by(user_id=current_user.id, verified=True): - mailboxes.append(mailbox.email) + mailboxes = current_user.mailboxes() if request.method == "POST": alias_prefix = request.form.get("prefix") diff --git a/app/models.py b/app/models.py index 344d23e8..1de69fa1 100644 --- a/app/models.py +++ b/app/models.py @@ -278,6 +278,18 @@ class User(db.Model, ModelMixin, UserMixin): def verified_custom_domains(self): return CustomDomain.query.filter_by(user_id=self.id, verified=True).all() + def mailboxes(self) -> [str]: + """list of mailbox emails that user own""" + if self.full_mailbox: + mailboxes = [] + else: + mailboxes = [self.email] + + for mailbox in Mailbox.query.filter_by(user_id=self.id, verified=True): + mailboxes.append(mailbox.email) + + return mailboxes + def __repr__(self): return f"" From bc0dc0265d9d1ea3313d7de055905563275eb2a4 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 13:41:59 +0700 Subject: [PATCH 03/24] Show mailbox when creating alias for user who has full_mailbox enabled --- app/dashboard/templates/dashboard/custom_alias.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/templates/dashboard/custom_alias.html b/app/dashboard/templates/dashboard/custom_alias.html index 53b9f08a..42279832 100644 --- a/app/dashboard/templates/dashboard/custom_alias.html +++ b/app/dashboard/templates/dashboard/custom_alias.html @@ -54,7 +54,7 @@ - {% if mailboxes|length > 1 %} + {% if mailboxes|length > 1 or current_user.full_mailbox %}
+ + + + {% for mailbox in mailboxes %} + + {% endfor %} + +
+ +
+ + + + +
+ +
+ + {% elif alias_info.mailbox != None %} +
+ Owned by {{ alias_info.mailbox.email }} mailbox +
+ {% endif %} + +
-
+
-
+
-
diff --git a/app/dashboard/views/index.py b/app/dashboard/views/index.py index f8a1b076..f55ad639 100644 --- a/app/dashboard/views/index.py +++ b/app/dashboard/views/index.py @@ -142,6 +142,31 @@ def index(): ) ) + elif request.form.get("form-name") == "set-mailbox": + gen_email_id = request.form.get("gen-email-id") + gen_email: GenEmail = GenEmail.get(gen_email_id) + mailbox_email = request.form.get("mailbox") + + mailbox = Mailbox.get_by(email=mailbox_email) + if not mailbox or mailbox.user_id != current_user.id: + flash("Something went wrong, please retry", "warning") + else: + gen_email.mailbox_id = mailbox.id + db.session.commit() + LOG.d("Set alias %s mailbox to %s", gen_email, mailbox) + + flash( + f"Update mailbox for {gen_email.email} to {mailbox_email}", + "success", + ) + return redirect( + url_for( + "dashboard.index", + highlight_gen_email_id=gen_email.id, + query=query, + ) + ) + return redirect(url_for("dashboard.index", query=query)) client_users = ( @@ -153,6 +178,8 @@ def index(): sorted(client_users, key=lambda cu: cu.client.name) + mailboxes = current_user.mailboxes() + return render_template( "dashboard/index.html", client_users=client_users, @@ -160,6 +187,7 @@ def index(): highlight_gen_email_id=highlight_gen_email_id, query=query, AliasGeneratorEnum=AliasGeneratorEnum, + mailboxes=mailboxes, ) diff --git a/app/models.py b/app/models.py index 1de69fa1..62b380df 100644 --- a/app/models.py +++ b/app/models.py @@ -162,6 +162,13 @@ class User(db.Model, ModelMixin, UserMixin): GenEmail.create_new(user.id, prefix="my-first-alias") db.session.flush() + # todo: uncomment when all existing users are full_mailbox + # to run just after migrating all existing user to full mailbox + # so new users are automatically full-mailbox + # Mailbox.create(user_id=user.id, email=user.email, verified=True) + # user.full_mailbox = True + # db.session.flush() + # Schedule onboarding emails Job.create( name=JOB_ONBOARDING_1, @@ -511,7 +518,7 @@ class GenEmail(db.Model, ModelMixin): mailbox = db.relationship("Mailbox") @classmethod - def create_new(cls, user_id, prefix, note=None): + def create_new(cls, user_id, prefix, note=None, mailbox_id=None): if not prefix: raise Exception("alias prefix cannot be empty") @@ -523,7 +530,9 @@ class GenEmail(db.Model, ModelMixin): if not cls.get_by(email=email): break - return GenEmail.create(user_id=user_id, email=email, note=note) + return GenEmail.create( + user_id=user_id, email=email, note=note, mailbox_id=mailbox_id + ) @classmethod def create_new_random( diff --git a/server.py b/server.py index 02790473..9098ec5f 100644 --- a/server.py +++ b/server.py @@ -132,6 +132,7 @@ def fake_data(): is_admin=True, otp_secret="base32secret3232", can_use_multiple_mailbox=True, + full_mailbox=True, ) db.session.commit() @@ -156,9 +157,13 @@ def fake_data(): api_key = ApiKey.create(user_id=user.id, name="Firefox") api_key.code = "codeFF" - GenEmail.create_new(user.id, "e1@") - GenEmail.create_new(user.id, "e2@") - GenEmail.create_new(user.id, "e3@") + m1 = Mailbox.create(user_id=user.id, email="m1@cd.ef", verified=True) + m2 = Mailbox.create(user_id=user.id, email="m2@zt.com", verified=False) + m3 = Mailbox.create(user_id=user.id, email="m3@cd.ef", verified=True) + db.session.commit() + + GenEmail.create_new(user.id, "e1@", mailbox_id=m1.id) + GenEmail.create_new(user.id, "e2@", mailbox_id=m3.id) CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True) CustomDomain.create( @@ -185,10 +190,6 @@ def fake_data(): client2.published = True db.session.commit() - Mailbox.create(user_id=user.id, email="ab@cd.ef", verified=True) - Mailbox.create(user_id=user.id, email="xy@zt.com", verified=False) - db.session.commit() - DeletedAlias.create(user_id=user.id, email="d1@ab.cd") DeletedAlias.create(user_id=user.id, email="d2@ab.cd") db.session.commit() From 18a6a50e388712945389cf23aecfcd13e069be94 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 14:02:02 +0700 Subject: [PATCH 06/24] Add Mailbox.new_email col --- app/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models.py b/app/models.py index 62b380df..712c6f0b 100644 --- a/app/models.py +++ b/app/models.py @@ -857,6 +857,9 @@ class Mailbox(db.Model, ModelMixin): email = db.Column(db.String(256), unique=True, nullable=False) verified = db.Column(db.Boolean, default=False, nullable=False) + # used when user wants to update mailbox email + new_email = db.Column(db.String(256), unique=True) + user = db.relationship(User) def nb_alias(self): From 571ff03115e20c420d046e929ae08b3b7b53114a Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 14:04:00 +0700 Subject: [PATCH 07/24] handle case where mailbox_email is the same as user email --- app/dashboard/views/custom_alias.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/dashboard/views/custom_alias.py b/app/dashboard/views/custom_alias.py index cdea83fc..d8ab8991 100644 --- a/app/dashboard/views/custom_alias.py +++ b/app/dashboard/views/custom_alias.py @@ -83,7 +83,8 @@ def custom_alias(): LOG.d("Set alias %s domain to %s", full_alias, custom_domain) gen_email.custom_domain_id = custom_domain.id - if mailbox_email != current_user.email: + # assign alias to a mailbox + if current_user.full_mailbox or mailbox_email != current_user.email: mailbox = Mailbox.get_by(email=mailbox_email) gen_email.mailbox_id = mailbox.id LOG.d("Set alias %s mailbox to %s", full_alias, mailbox) From aa784a05934abc27c7ba1a54ef08ffbd0ca84be8 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 14:04:20 +0700 Subject: [PATCH 08/24] use red text for delete button --- app/dashboard/templates/dashboard/directory.html | 2 +- app/dashboard/templates/dashboard/mailbox.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/dashboard/templates/dashboard/directory.html b/app/dashboard/templates/dashboard/directory.html index ccc1edcd..90da7bed 100644 --- a/app/dashboard/templates/dashboard/directory.html +++ b/app/dashboard/templates/dashboard/directory.html @@ -57,7 +57,7 @@ - + Delete diff --git a/app/dashboard/templates/dashboard/mailbox.html b/app/dashboard/templates/dashboard/mailbox.html index bfa07221..be3c4b24 100644 --- a/app/dashboard/templates/dashboard/mailbox.html +++ b/app/dashboard/templates/dashboard/mailbox.html @@ -56,7 +56,7 @@ - + Delete From 731d898774a7457cb4a824ff926ff651346daf27 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 14:08:55 +0700 Subject: [PATCH 09/24] Handle mailbox change --- app/dashboard/__init__.py | 1 + .../templates/dashboard/mailbox.html | 14 +- .../templates/dashboard/mailbox_detail.html | 58 +++++++ app/dashboard/views/mailbox_detail.py | 150 ++++++++++++++++++ .../transactional/verify-mailbox-change.html | 11 ++ .../transactional/verify-mailbox-change.txt | 10 ++ 6 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 app/dashboard/templates/dashboard/mailbox_detail.html create mode 100644 app/dashboard/views/mailbox_detail.py create mode 100644 templates/emails/transactional/verify-mailbox-change.html create mode 100644 templates/emails/transactional/verify-mailbox-change.txt diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index 2f432540..3d981918 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -16,4 +16,5 @@ from .views import ( directory, mailbox, deleted_alias, + mailbox_detail, ) diff --git a/app/dashboard/templates/dashboard/mailbox.html b/app/dashboard/templates/dashboard/mailbox.html index be3c4b24..95589d31 100644 --- a/app/dashboard/templates/dashboard/mailbox.html +++ b/app/dashboard/templates/dashboard/mailbox.html @@ -20,13 +20,15 @@ A mailbox is just another personal email address. When creating a new alias, you could choose the mailbox that owns this alias, i.e:
- all emails sent to this alias will be forwarded to this mailbox
- - from this mailbox, you can reply/send emails from the alias.
+ - from this mailbox, you can reply/send emails from the alias.

- By default, all aliases are owned by your email {{ current_user.email }}.

+ {% if current_user.full_mailbox %} + When you signed up, a mailbox is automatically created with your email {{ current_user.email }} +

+ {% endif %} The mailbox doesn't have to be your email: it can be your friend's email - if you want to create aliases for your buddy.
- They just need to validate this mailbox when they receive the activation email. + if you want to create aliases for your buddy.
{% for mailbox in mailboxes %} @@ -45,8 +47,10 @@
Created {{ mailbox.created_at | dt }}
- {{ mailbox.nb_alias() }} aliases. + {{ mailbox.nb_alias() }} aliases.
+ + Edit ➡
- {% elif alias_info.mailbox != None %} + {% elif alias_info.mailbox != None and alias_info.mailbox.email != current_user.email %}
Owned by {{ alias_info.mailbox.email }} mailbox
From 0a5113962f890539a2382a9c633bd9043611c1ba Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 15:10:59 +0700 Subject: [PATCH 15/24] remove autofocus on directory & mailbox --- app/dashboard/templates/dashboard/directory.html | 3 +-- app/dashboard/templates/dashboard/mailbox.html | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/dashboard/templates/dashboard/directory.html b/app/dashboard/templates/dashboard/directory.html index 90da7bed..9794c387 100644 --- a/app/dashboard/templates/dashboard/directory.html +++ b/app/dashboard/templates/dashboard/directory.html @@ -85,8 +85,7 @@ {{ new_dir_form.name(class="form-control", placeholder="my-directory", pattern="[0-9a-z-_]{3,}", - title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.", - autofocus="1") }} + title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.") }} {{ render_field_errors(new_dir_form.name) }} diff --git a/app/dashboard/templates/dashboard/mailbox.html b/app/dashboard/templates/dashboard/mailbox.html index 95589d31..12727d32 100644 --- a/app/dashboard/templates/dashboard/mailbox.html +++ b/app/dashboard/templates/dashboard/mailbox.html @@ -85,8 +85,7 @@ A verification email will be sent to this email to make sure you have access to this email.
- {{ new_mailbox_form.email(class="form-control", placeholder="email@example.com", - autofocus="1") }} + {{ new_mailbox_form.email(class="form-control", placeholder="email@example.com") }} {{ render_field_errors(new_mailbox_form.email) }} From 9669c0441416c7f887384ea760145f6202501d82 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 15:40:41 +0700 Subject: [PATCH 16/24] Add user.default_mailbox_id col --- app/models.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models.py b/app/models.py index 712c6f0b..d6c36329 100644 --- a/app/models.py +++ b/app/models.py @@ -145,6 +145,11 @@ class User(db.Model, ModelMixin, UserMixin): db.Boolean, default=False, nullable=False, server_default="0" ) + # the mailbox used when create random alias + default_mailbox_id = db.Column( + db.ForeignKey("mailbox.id"), nullable=True, default=None + ) + profile_picture = db.relationship(File) @classmethod @@ -165,9 +170,10 @@ class User(db.Model, ModelMixin, UserMixin): # todo: uncomment when all existing users are full_mailbox # to run just after migrating all existing user to full mailbox # so new users are automatically full-mailbox - # Mailbox.create(user_id=user.id, email=user.email, verified=True) - # user.full_mailbox = True + # mb = Mailbox.create(user_id=user.id, email=user.email, verified=True) # db.session.flush() + # user.full_mailbox = True + # user.default_mailbox_id = mb.id # Schedule onboarding emails Job.create( @@ -860,8 +866,6 @@ class Mailbox(db.Model, ModelMixin): # used when user wants to update mailbox email new_email = db.Column(db.String(256), unique=True) - user = db.relationship(User) - def nb_alias(self): return GenEmail.filter_by(mailbox_id=self.id).count() From 2eae0ba4fd54fd040cd4dbf7c8c90f1fda281bf5 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 15:41:08 +0700 Subject: [PATCH 17/24] set user default mailbox in convert_user_full_mailbox --- server.py | 2 ++ shell.py | 1 + 2 files changed, 3 insertions(+) diff --git a/server.py b/server.py index 9098ec5f..f636427b 100644 --- a/server.py +++ b/server.py @@ -162,6 +162,8 @@ def fake_data(): m3 = Mailbox.create(user_id=user.id, email="m3@cd.ef", verified=True) db.session.commit() + user.default_mailbox_id = m1.id + GenEmail.create_new(user.id, "e1@", mailbox_id=m1.id) GenEmail.create_new(user.id, "e2@", mailbox_id=m3.id) diff --git a/shell.py b/shell.py index 1f12c28d..39607d0e 100644 --- a/shell.py +++ b/shell.py @@ -54,6 +54,7 @@ def convert_user_full_mailbox(user): # finally set user to full_mailbox user.full_mailbox = True + user.default_mailbox_id = default_mb.id db.session.commit() From 7febe6e15b94624190cf75b2c35e3c064aae7462 Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 15:41:53 +0700 Subject: [PATCH 18/24] cannot delete default mailbox --- app/dashboard/templates/dashboard/mailbox.html | 11 +++++++++-- app/dashboard/views/mailbox.py | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/dashboard/templates/dashboard/mailbox.html b/app/dashboard/templates/dashboard/mailbox.html index 12727d32..b86affe8 100644 --- a/app/dashboard/templates/dashboard/mailbox.html +++ b/app/dashboard/templates/dashboard/mailbox.html @@ -43,11 +43,17 @@ 🚫 {% endif %} - + {% if mailbox.id == current_user.default_mailbox_id %} +
Default Mailbox +
+ {% endif %} +
Created {{ mailbox.created_at | dt }}
{{ mailbox.nb_alias() }} aliases.
+
Edit ➡ @@ -60,7 +66,8 @@ - + Delete diff --git a/app/dashboard/views/mailbox.py b/app/dashboard/views/mailbox.py index 5ce2ed5e..796c0001 100644 --- a/app/dashboard/views/mailbox.py +++ b/app/dashboard/views/mailbox.py @@ -44,6 +44,10 @@ def mailbox_route(): flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) + if mailbox.id == current_user.default_mailbox_id: + flash("You cannot delete default mailbox", "error") + return redirect(url_for("dashboard.mailbox_route")) + email = mailbox.email Mailbox.delete(mailbox_id) db.session.commit() From 1fcbe329902fcd2e439f622cbe681cb213da154b Mon Sep 17 00:00:00 2001 From: Son NK Date: Sun, 23 Feb 2020 15:51:26 +0700 Subject: [PATCH 19/24] Can set a mailbox as default --- .../templates/dashboard/mailbox.html | 17 ++++++++++++++- app/dashboard/views/mailbox.py | 21 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/dashboard/templates/dashboard/mailbox.html b/app/dashboard/templates/dashboard/mailbox.html index b86affe8..1d4d84f0 100644 --- a/app/dashboard/templates/dashboard/mailbox.html +++ b/app/dashboard/templates/dashboard/mailbox.html @@ -45,7 +45,7 @@ {% endif %} {% if mailbox.id == current_user.default_mailbox_id %}
Default Mailbox + title="When a new random alias is created, it belongs to the default mailbox">Default Mailbox
{% endif %} @@ -61,6 +61,21 @@