diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b6b1019fa..6dc29eae6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,6 +68,12 @@ For most tests, you will need to have ``redis`` installed and started on your ma sh scripts/run-test.sh ``` +You can also run tests using a local Postgres DB to speed things up. This can be done by + +- creating an empty test DB and running the database migration by `dropdb test && createdb test && DB_URI=postgresql://localhost:5432/test alembic upgrade head` + +- replacing the `DB_URI` in `test.env` file by `DB_URI=postgresql://localhost:5432/test` + ## Run the code locally Install npm packages diff --git a/app/dashboard/views/setting.py b/app/dashboard/views/setting.py index cefbaf58a..f07ebb17a 100644 --- a/app/dashboard/views/setting.py +++ b/app/dashboard/views/setting.py @@ -227,6 +227,21 @@ def setting(): Session.commit() flash("Your preference has been updated", "success") return redirect(url_for("dashboard.setting")) + elif request.form.get("form-name") == "enable_data_breach_check": + if not current_user.is_premium(): + flash("Only premium plan can enable data breach monitoring", "warning") + return redirect(url_for("dashboard.setting")) + choose = request.form.get("enable_data_breach_check") + if choose == "on": + LOG.i("User {current_user} has enabled data breach monitoring") + current_user.enable_data_breach_check = True + flash("Data breach monitoring is enabled", "success") + else: + LOG.i("User {current_user} has disabled data breach monitoring") + current_user.enable_data_breach_check = False + flash("Data breach monitoring is disabled", "info") + Session.commit() + return redirect(url_for("dashboard.setting")) elif request.form.get("form-name") == "sender-in-ra": choose = request.form.get("enable") if choose == "on": diff --git a/app/models.py b/app/models.py index 472b98a85..25d77bf39 100644 --- a/app/models.py +++ b/app/models.py @@ -525,6 +525,11 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle): sa.Boolean, default=True, nullable=False, server_default="1" ) + # user opted in for data breach check + enable_data_breach_check = sa.Column( + sa.Boolean, default=False, nullable=False, server_default="0" + ) + # bitwise flags. Allow for future expansion flags = sa.Column( sa.BigInteger, diff --git a/cron.py b/cron.py index 7466af780..2f1e6c886 100644 --- a/cron.py +++ b/cron.py @@ -1070,6 +1070,7 @@ def get_alias_to_check_hibp( Alias.id >= min_alias_id, Alias.id < max_alias_id, User.disabled == False, # noqa: E712 + User.enable_data_breach_check, or_( User.lifetime, ManualSubscription.end_at > now, diff --git a/migrations/versions/2024_040913_fa2f19bb4e5a_.py b/migrations/versions/2024_040913_fa2f19bb4e5a_.py new file mode 100644 index 000000000..509198880 --- /dev/null +++ b/migrations/versions/2024_040913_fa2f19bb4e5a_.py @@ -0,0 +1,29 @@ +"""empty message + +Revision ID: fa2f19bb4e5a +Revises: 52510a633d6f +Create Date: 2024-04-09 13:12:26.305340 + +""" +import sqlalchemy_utils +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'fa2f19bb4e5a' +down_revision = '52510a633d6f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('enable_data_breach_check', sa.Boolean(), server_default='0', nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('users', 'enable_data_breach_check') + # ### end Alembic commands ### diff --git a/templates/dashboard/setting.html b/templates/dashboard/setting.html index 03403766f..ab6d865cb 100644 --- a/templates/dashboard/setting.html +++ b/templates/dashboard/setting.html @@ -249,6 +249,42 @@ + +
+
+
Data breach monitoring
+
+ {% if not current_user.is_premium() %} + + + {% endif %} + If enabled, we will inform you via email if one of your aliases appears in a data breach. +
+ SimpleLogin uses HaveIBeenPwned API for checking for data breaches. +
+
+ {{ csrf_form.csrf_token }} + +
+ + +
+ +
+
+
+
@@ -285,7 +321,9 @@ No Name (i.e. only reverse-alias) - +
@@ -295,7 +333,9 @@
Reverse Alias Replacement -
Experimental
+
+ Experimental +
When replying to a forwarded email, the reverse-alias can be automatically included @@ -312,9 +352,13 @@ name="replace-ra" {% if current_user.replace_reverse_alias %} checked{% endif %} class="form-check-input"> - +
- +
diff --git a/tests/cron/test_get_alias_for_hibp.py b/tests/cron/test_get_alias_for_hibp.py index ba41666c2..370c6ff8d 100644 --- a/tests/cron/test_get_alias_for_hibp.py +++ b/tests/cron/test_get_alias_for_hibp.py @@ -31,6 +31,7 @@ def test_get_alias_for_free_user_has_no_alias(): def test_get_alias_for_lifetime_with_null_hibp_date(): user = create_new_user() user.lifetime = True + user.enable_data_breach_check = True alias_id = Alias.create_new_random(user).id Session.commit() aliases = list( @@ -42,6 +43,7 @@ def test_get_alias_for_lifetime_with_null_hibp_date(): def test_get_alias_for_lifetime_with_old_hibp_date(): user = create_new_user() user.lifetime = True + user.enable_data_breach_check = True alias = Alias.create_new_random(user) alias.hibp_last_check = arrow.now().shift(days=-1) alias_id = alias.id @@ -97,6 +99,7 @@ def create_partner_sub(user: User): @pytest.mark.parametrize("sub_generator", sub_generator_list) def test_get_alias_for_sub(sub_generator): user = create_new_user() + user.enable_data_breach_check = True sub_generator(user) alias_id = Alias.create_new_random(user).id Session.commit() @@ -140,3 +143,26 @@ def test_already_checked_is_not_checked(): cron.get_alias_to_check_hibp(arrow.now(), [user.id], alias_id, alias_id + 1) ) assert len(aliases) == 0 + + +def test_outed_in_user_is_checked(): + user = create_new_user() + user.lifetime = True + user.enable_data_breach_check = True + alias_id = Alias.create_new_random(user).id + Session.commit() + aliases = list( + cron.get_alias_to_check_hibp(arrow.now(), [], alias_id, alias_id + 1) + ) + assert len(aliases) == 1 + + +def test_outed_out_user_is_not_checked(): + user = create_new_user() + user.lifetime = True + alias_id = Alias.create_new_random(user).id + Session.commit() + aliases = list( + cron.get_alias_to_check_hibp(arrow.now(), [], alias_id, alias_id + 1) + ) + assert len(aliases) == 0