From c593253c7d4d0c48ec960c923842c41718edc4aa Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 00:04:59 +0200 Subject: [PATCH 01/10] Add pgp_public_key,pgp_finger_print columns to Contact model --- app/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models.py b/app/models.py index 0bc40075..8172e71c 100644 --- a/app/models.py +++ b/app/models.py @@ -933,9 +933,16 @@ class Contact(db.Model, ModelMixin): # whether a contact is created via CC is_cc = db.Column(db.Boolean, nullable=False, default=False, server_default="0") + pgp_public_key = db.Column(db.Text, nullable=True) + pgp_finger_print = db.Column(db.String(512), nullable=True) + alias = db.relationship(Alias) user = db.relationship(User) + @property + def email(self): + return self.website_email + def website_send_to(self): """return the email address with name. to use when user wants to send an email from the alias From afe975b8c3ebfb7efc13ee96c8f2edf2a1602dfb Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 00:07:46 +0200 Subject: [PATCH 02/10] User can add PGP key to for a contact --- app/dashboard/__init__.py | 1 + .../templates/dashboard/contact_detail.html | 65 +++++++++++++++++++ app/dashboard/views/contact_detail.py | 55 ++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 app/dashboard/templates/dashboard/contact_detail.html create mode 100644 app/dashboard/views/contact_detail.py diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index fc216406..41ff2bc0 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -22,4 +22,5 @@ from .views import ( refused_email, referral, recovery_code, + contact_detail, ) diff --git a/app/dashboard/templates/dashboard/contact_detail.html b/app/dashboard/templates/dashboard/contact_detail.html new file mode 100644 index 00000000..af0b1fd8 --- /dev/null +++ b/app/dashboard/templates/dashboard/contact_detail.html @@ -0,0 +1,65 @@ +{% extends 'default.html' %} + +{% set active_page = "dashboard" %} + +{% block title %} + Contact {{ contact.email }} - Alias {{ alias.email }} +{% endblock %} + +{% block default_content %} + +
+
+

{{ alias.email }} / {{ contact.email }} + {% if contact.pgp_finger_print %} + 🗝 + {% endif %} +

+ +
+
+ + +
+
+ Pretty Good Privacy (PGP) +
+ By importing your contact PGP Public Key into SimpleLogin, all emails sent to + {{ contact.email }} from your alias {{ alias.email }} + are encrypted. +
+
+ + {% if not current_user.is_premium() %} + + {% endif %} + +
+ + + +
+ + + {% if contact.pgp_finger_print %} + + {% endif %} + +
+
+ +
+ +
+
+{% endblock %} + + diff --git a/app/dashboard/views/contact_detail.py b/app/dashboard/views/contact_detail.py new file mode 100644 index 00000000..ead29120 --- /dev/null +++ b/app/dashboard/views/contact_detail.py @@ -0,0 +1,55 @@ +from flask import render_template, request, redirect, url_for, flash +from flask_login import login_required, current_user + +from app.dashboard.base import dashboard_bp +from app.extensions import db +from app.models import Contact +from app.pgp_utils import PGPException, load_public_key + + +@dashboard_bp.route("/contact//", methods=["GET", "POST"]) +@login_required +def contact_detail_route(contact_id): + contact = Contact.get(contact_id) + if not contact or contact.user_id != current_user.id: + flash("You cannot see this page", "warning") + return redirect(url_for("dashboard.index")) + + alias = contact.alias + + if request.method == "POST": + if request.form.get("form-name") == "pgp": + if request.form.get("action") == "save": + if not current_user.is_premium(): + flash("Only premium plan can add PGP Key", "warning") + return redirect( + url_for("dashboard.contact_detail_route", contact_id=contact_id) + ) + + contact.pgp_public_key = request.form.get("pgp") + try: + contact.pgp_finger_print = load_public_key(contact.pgp_public_key) + except PGPException: + flash("Cannot add the public key, please verify it", "error") + else: + db.session.commit() + flash( + f"PGP public key for {contact.email} is saved successfully", + "success", + ) + return redirect( + url_for("dashboard.contact_detail_route", contact_id=contact_id) + ) + elif request.form.get("action") == "remove": + # Free user can decide to remove contact PGP key + contact.pgp_public_key = None + contact.pgp_finger_print = None + db.session.commit() + flash(f"PGP public key for {contact.email} is removed", "success") + return redirect( + url_for("dashboard.contact_detail_route", contact_id=contact_id) + ) + + return render_template( + "dashboard/contact_detail.html", contact=contact, alias=alias + ) From b962d6a2c1bd4460a430fa7b4b6be75b28f37ece Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 00:08:49 +0200 Subject: [PATCH 03/10] encrypt sent email if contact has PGP enabled --- email_handler.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/email_handler.py b/email_handler.py index f9868635..d2487630 100644 --- a/email_handler.py +++ b/email_handler.py @@ -605,6 +605,11 @@ def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (bool, str if custom_domain.dkim_verified: add_dkim_signature(msg, alias_domain) + # create PGP email if needed + if contact.pgp_finger_print and user.is_premium(): + LOG.d("Encrypt message for contact %s", contact) + msg = prepare_pgp_message(msg, contact.pgp_finger_print) + smtp.sendmail( alias.email, contact.website_email, From 016d342f3b9a83605479ae11532f4af09528f42b Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 00:09:06 +0200 Subject: [PATCH 04/10] load contact pgp keys in load_pgp_public_keys --- init_app.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/init_app.py b/init_app.py index 22389fc2..b77088ea 100644 --- a/init_app.py +++ b/init_app.py @@ -1,5 +1,5 @@ """Initial loading script""" -from app.models import Mailbox +from app.models import Mailbox, Contact from app.log import LOG from app.extensions import db from app.pgp_utils import load_public_key @@ -16,6 +16,16 @@ def load_pgp_public_keys(): if fingerprint != mailbox.pgp_finger_print: LOG.error("fingerprint %s different for mailbox %s", fingerprint, mailbox) mailbox.pgp_finger_print = fingerprint + db.session.commit() + + for contact in Contact.query.filter(Contact.pgp_public_key != None).all(): + LOG.d("Load PGP key for %s", contact) + fingerprint = load_public_key(contact.pgp_public_key) + + # sanity check + if fingerprint != contact.pgp_finger_print: + LOG.error("fingerprint %s different for contact %s", fingerprint, contact) + contact.pgp_finger_print = fingerprint db.session.commit() From f708ee6bb2d4d1e68a77ab0891eb9d19b8f3685a Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 00:09:51 +0200 Subject: [PATCH 05/10] improve wordings in alias contact manager page --- .../dashboard/alias_contact_manager.html | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/app/dashboard/templates/dashboard/alias_contact_manager.html b/app/dashboard/templates/dashboard/alias_contact_manager.html index 9e201d8b..5f1ccf8d 100644 --- a/app/dashboard/templates/dashboard/alias_contact_manager.html +++ b/app/dashboard/templates/dashboard/alias_contact_manager.html @@ -9,7 +9,7 @@ {% block default_content %}
-

{{ alias.email }} +

{{ alias.email }} contacts
+
+
+ + + + Delete + +
+
From bc464d354902220054ec3cb9c560cba7852696e3 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 09:29:27 +0200 Subject: [PATCH 06/10] migration script --- .../versions/2020_060700_a5b4dc311a89_.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 migrations/versions/2020_060700_a5b4dc311a89_.py diff --git a/migrations/versions/2020_060700_a5b4dc311a89_.py b/migrations/versions/2020_060700_a5b4dc311a89_.py new file mode 100644 index 00000000..7e9afbcd --- /dev/null +++ b/migrations/versions/2020_060700_a5b4dc311a89_.py @@ -0,0 +1,31 @@ +"""empty message + +Revision ID: a5b4dc311a89 +Revises: 749c2b85d20f +Create Date: 2020-06-07 00:08:08.588009 + +""" +import sqlalchemy_utils +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a5b4dc311a89' +down_revision = '749c2b85d20f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('contact', sa.Column('pgp_finger_print', sa.String(length=512), nullable=True)) + op.add_column('contact', sa.Column('pgp_public_key', sa.Text(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('contact', 'pgp_public_key') + op.drop_column('contact', 'pgp_finger_print') + # ### end Alembic commands ### From 7b2d86552bf1fb5bd2ec25f4efc4880112709076 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 11:59:45 +0200 Subject: [PATCH 07/10] fix popup display when edit contact --- app/dashboard/templates/dashboard/alias_contact_manager.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/templates/dashboard/alias_contact_manager.html b/app/dashboard/templates/dashboard/alias_contact_manager.html index 5f1ccf8d..7edfda41 100644 --- a/app/dashboard/templates/dashboard/alias_contact_manager.html +++ b/app/dashboard/templates/dashboard/alias_contact_manager.html @@ -98,7 +98,7 @@
From 6cccb450b027a7f8d56a5b45f664a8fe0ad0ce45 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 12:10:33 +0200 Subject: [PATCH 08/10] fix contact order on alias contact manager page --- app/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models.py b/app/models.py index 8172e71c..7cebed23 100644 --- a/app/models.py +++ b/app/models.py @@ -805,7 +805,7 @@ class Alias(db.Model, ModelMixin): def get_contacts(self, page=0): contacts = ( Contact.filter_by(alias_id=self.id) - .order_by(Contact.created_at) + .order_by(Contact.created_at.desc()) .limit(PAGE_LIMIT) .offset(page * PAGE_LIMIT) .all() From 08f4891492e222fd11181b4e9dced1c543f92369 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 12:11:00 +0200 Subject: [PATCH 09/10] use breadcrumb for contact header --- .../templates/dashboard/contact_detail.html | 16 ++++++++++++---- static/darkmode.css | 7 +++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/dashboard/templates/dashboard/contact_detail.html b/app/dashboard/templates/dashboard/contact_detail.html index af0b1fd8..25a79776 100644 --- a/app/dashboard/templates/dashboard/contact_detail.html +++ b/app/dashboard/templates/dashboard/contact_detail.html @@ -10,10 +10,18 @@
-

{{ alias.email }} / {{ contact.email }} - {% if contact.pgp_finger_print %} - 🗝 - {% endif %} +

+

diff --git a/static/darkmode.css b/static/darkmode.css index 66c696e1..795f6f88 100644 --- a/static/darkmode.css +++ b/static/darkmode.css @@ -10,6 +10,7 @@ --heading-background: #FFF; --border: 1px solid rgba(0, 40, 100, 0.12); --input-bg-color: var(--white); + --light-bg-color: #e9ecef; } [data-theme="dark"] { @@ -21,6 +22,7 @@ --heading-background: #1a1a1a; --input-bg-color: #4c4c4c; --border: 1px solid rgba(228, 236, 238, 0.35); + --light-bg-color: #5c5c5c; } /** Override the bootstrap color configurations */ @@ -46,6 +48,11 @@ hr { background-color: var(--input-bg-color); } +.breadcrumb { + color: var(--font-color); + background-color: var(--light-bg-color); +} + .form-control:focus, .dataTables_wrapper .dataTables_length select:focus, .dataTables_wrapper .dataTables_filter input:focus, .modal-content { border-color: #1991eb; outline: 0; From d85b32d56f127bf78d1ffbc1c3203da91ad9449b Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sun, 7 Jun 2020 12:14:40 +0200 Subject: [PATCH 10/10] prettify contact manager page --- .../dashboard/alias_contact_manager.html | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/app/dashboard/templates/dashboard/alias_contact_manager.html b/app/dashboard/templates/dashboard/alias_contact_manager.html index 7edfda41..d3c3e5bc 100644 --- a/app/dashboard/templates/dashboard/alias_contact_manager.html +++ b/app/dashboard/templates/dashboard/alias_contact_manager.html @@ -95,22 +95,15 @@ {% endif %}
-
-
- Edit -
-
-
- - - + Edit ➡ + + + + + Delete - -
-
+