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() %} +