From fb4cb8727cfb49976fc3efe1c5930f125e3b9b78 Mon Sep 17 00:00:00 2001 From: Son Nguyen Kim Date: Thu, 29 Jul 2021 09:35:00 +0200 Subject: [PATCH] Add notify_hibp cron job --- cron.py | 46 +++++++++++++++++++ crontab.yml | 7 +++ server.py | 17 +++++++ .../transactional/hibp-new-breaches.html | 46 +++++++++++++++++++ .../hibp-new-breaches.txt.jinja2 | 23 ++++++++++ 5 files changed, 139 insertions(+) create mode 100644 templates/emails/transactional/hibp-new-breaches.html create mode 100644 templates/emails/transactional/hibp-new-breaches.txt.jinja2 diff --git a/cron.py b/cron.py index 1f25dae7..3529d2e2 100644 --- a/cron.py +++ b/cron.py @@ -7,6 +7,7 @@ from typing import List, Tuple import arrow import requests from sqlalchemy import func, desc, or_ +from sqlalchemy.orm import joinedload from app import s3 from app.alias_utils import nb_email_log_for_mailbox @@ -771,6 +772,47 @@ async def check_hibp(): LOG.d("Done checking HIBP API for aliases in breaches") +def notify_hibp(): + """ + Send aggregated email reports for HIBP breaches + """ + # to get a list of users that have at least a breached alias + alias_query = ( + db.session.query(Alias) + .options(joinedload(Alias.hibp_breaches)) + .filter(Alias.hibp_breaches.any()) + .distinct(Alias.user_id) + .all() + ) + + user_ids = [alias.user_id for alias in alias_query] + + for user in User.query.filter(User.id.in_(user_ids)): + breached_aliases = ( + db.session.query(Alias) + .options(joinedload(Alias.hibp_breaches)) + .filter(Alias.hibp_breaches.any(), Alias.user_id == user.id) + .all() + ) + + LOG.d(f"Send new breaches found email to user {user}") + + send_email( + user.email, + f"You were in a data breach", + render( + "transactional/hibp-new-breaches.txt.jinja2", + user=user, + breached_aliases=breached_aliases, + ), + render( + "transactional/hibp-new-breaches.html", + user=user, + breached_aliases=breached_aliases, + ), + ) + + if __name__ == "__main__": LOG.d("Start running cronjob") parser = argparse.ArgumentParser() @@ -790,6 +832,7 @@ if __name__ == "__main__": "delete_old_monitoring", "check_custom_domain", "check_hibp", + "notify_hibp", ], ) args = parser.parse_args() @@ -827,3 +870,6 @@ if __name__ == "__main__": elif args.job == "check_hibp": LOG.d("Check HIBP") asyncio.run(check_hibp()) + elif args.job == "notify_hibp": + LOG.d("Notify users about HIBP breaches") + notify_hibp() diff --git a/crontab.yml b/crontab.yml index d22f14c2..90fe4671 100644 --- a/crontab.yml +++ b/crontab.yml @@ -58,4 +58,11 @@ jobs: shell: /bin/bash schedule: "0 18 * * *" captureStderr: true + concurrencyPolicy: Forbid + + - name: SimpleLogin Notify HIBP breaches + command: python /code/cron.py -j notify_hibp + shell: /bin/bash + schedule: "0 19 * * *" + captureStderr: true concurrencyPolicy: Forbid \ No newline at end of file diff --git a/server.py b/server.py index be3d177f..15bda4b9 100644 --- a/server.py +++ b/server.py @@ -102,6 +102,8 @@ from app.models import ( Payout, Coupon, SLDomain, + Hibp, + AliasHibp, ) from app.monitor.base import monitor_bp from app.oauth.base import oauth_bp @@ -421,6 +423,21 @@ def fake_data(): SLDomain.create(domain="premium.com", premium_only=True, commit=True) + hibp1 = Hibp.create( + name="first breach", description="breach description", commit=True + ) + hibp2 = Hibp.create( + name="second breach", description="breach description", commit=True + ) + breached_alias1 = Alias.create( + email="john@example.com", user_id=user.id, mailbox_id=m1.id, commit=True + ) + breached_alias2 = Alias.create( + email="wick@example.com", user_id=user.id, mailbox_id=m1.id, commit=True + ) + AliasHibp.create(hibp_id=hibp1.id, alias_id=breached_alias1.id) + AliasHibp.create(hibp_id=hibp2.id, alias_id=breached_alias2.id) + @login_manager.user_loader def load_user(user_id): diff --git a/templates/emails/transactional/hibp-new-breaches.html b/templates/emails/transactional/hibp-new-breaches.html new file mode 100644 index 00000000..eafba7bc --- /dev/null +++ b/templates/emails/transactional/hibp-new-breaches.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% block content %} + {% call text() %} +

+ {{ breached_aliases|count }} of your aliases are found in data breaches. +

+ {% endcall %} + +
    + {%- for alias in breached_aliases[:10] %} +
  1. {% call text() %} + {{ alias.email }} was found in {{ alias.hibp_breaches|count }} data breaches.
    + +
      + {% set breaches = alias.hibp_breaches|sort(attribute='date', reverse=True) %} + {%- for breach in breaches[:4] %} +
    • + {{ breach.name }} {% if breach.date %}({{ breach.date.format('YYYY-MM-DD') }}){% endif %} + {{ breach.description }} +
    • + {%- endfor %} +
    + + {% if breaches|length > 4 %} + And {{ breaches|length - 4 }} more data breaches... + {% endif %} + + {% endcall %}
  2. + {%- endfor %} + +
+ + {% if breached_aliases|length > 10 %} + {% call text() %} + And {{ breached_aliases|length - 10 }} more aliases... + {% endcall %} + {% endif %} + + {% call text() %} + For more information, check HaveIBeenPwned.com. + {% endcall %} + + + {{ render_text('Best,
SimpleLogin Team.') }} +{% endblock %} \ No newline at end of file diff --git a/templates/emails/transactional/hibp-new-breaches.txt.jinja2 b/templates/emails/transactional/hibp-new-breaches.txt.jinja2 new file mode 100644 index 00000000..e78ffc1a --- /dev/null +++ b/templates/emails/transactional/hibp-new-breaches.txt.jinja2 @@ -0,0 +1,23 @@ +{{ breached_aliases|count }} of your aliases are found in data breaches. + +{% for alias in breached_aliases[:10] %} + {{ loop.index }} ) {{ alias.email }} was found in {{ alias.hibp_breaches|count }} data breaches. + + {%- set breaches = alias.hibp_breaches|sort(attribute='date', reverse=True) %} + {% for breach in breaches[:4] %} + - {{ breach.name }} {% if breach.date %}({{ breach.date.format('YYYY-MM-DD') }}){% endif %} + {%- endfor %} + + {%- if breaches|length > 4 %} + And {{ breaches|length - 4 }} more data breaches... + {% endif %} +{% endfor %} + +{%- if breached_aliases|length > 10 %} + And {{ breached_aliases|length - 10 }} more aliases... +{%- endif %} + +For more information, please check https://haveibeenpwned.com/. + +Best, +SimpleLogin Team.