diff --git a/app/app/admin_model.py b/app/app/admin_model.py index 36c69c7..98844bd 100644 --- a/app/app/admin_model.py +++ b/app/app/admin_model.py @@ -214,6 +214,20 @@ class UserAdmin(SLModelView): Session.commit() + @action( + "remove trial", + "Stop trial period", + "Remove trial for this user?", + ) + def stop_trial(self, ids): + for user in User.filter(User.id.in_(ids)): + user.trial_end = None + + flash(f"Stopped trial for {user}", "success") + AdminAuditLog.stop_trial(current_user.id, user.id) + + Session.commit() + @action( "disable_otp_fido", "Disable OTP & FIDO", diff --git a/app/app/api/views/apple.py b/app/app/api/views/apple.py index 8b5125b..5e41e05 100644 --- a/app/app/api/views/apple.py +++ b/app/app/api/views/apple.py @@ -17,9 +17,14 @@ from app.models import PlanEnum, AppleSubscription _MONTHLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.monthly" _YEARLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.yearly" +# SL Mac app used to be in SL account _MACAPP_MONTHLY_PRODUCT_ID = "io.simplelogin.macapp.subscription.premium.monthly" _MACAPP_YEARLY_PRODUCT_ID = "io.simplelogin.macapp.subscription.premium.yearly" +# SL Mac app is moved to Proton account +_MACAPP_MONTHLY_PRODUCT_ID_NEW = "me.proton.simplelogin.macos.premium.monthly" +_MACAPP_YEARLY_PRODUCT_ID_NEW = "me.proton.simplelogin.macos.premium.yearly" + # Apple API URL _SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt" _PROD_URL = "https://buy.itunes.apple.com/verifyReceipt" @@ -263,7 +268,11 @@ def apple_update_notification(): plan = ( PlanEnum.monthly if transaction["product_id"] - in (_MONTHLY_PRODUCT_ID, _MACAPP_MONTHLY_PRODUCT_ID) + in ( + _MONTHLY_PRODUCT_ID, + _MACAPP_MONTHLY_PRODUCT_ID, + _MACAPP_MONTHLY_PRODUCT_ID_NEW, + ) else PlanEnum.yearly ) @@ -517,7 +526,11 @@ def verify_receipt(receipt_data, user, password) -> Optional[AppleSubscription]: plan = ( PlanEnum.monthly if latest_transaction["product_id"] - in (_MONTHLY_PRODUCT_ID, _MACAPP_MONTHLY_PRODUCT_ID) + in ( + _MONTHLY_PRODUCT_ID, + _MACAPP_MONTHLY_PRODUCT_ID, + _MACAPP_MONTHLY_PRODUCT_ID_NEW, + ) else PlanEnum.yearly ) diff --git a/app/app/models.py b/app/app/models.py index 2eb786e..ac12b9c 100644 --- a/app/app/models.py +++ b/app/app/models.py @@ -235,6 +235,7 @@ class AuditLogActionEnum(EnumE): download_provider_complaint = 8 disable_user = 9 enable_user = 10 + stop_trial = 11 class Phase(EnumE): @@ -3339,6 +3340,15 @@ class AdminAuditLog(Base): }, ) + @classmethod + def stop_trial(cls, admin_user_id: int, user_id: int): + cls.create( + admin_user_id=admin_user_id, + action=AuditLogActionEnum.stop_trial.value, + model="User", + model_id=user_id, + ) + @classmethod def disable_otp_fido( cls, admin_user_id: int, user_id: int, had_otp: bool, had_fido: bool diff --git a/app/app/rate_limiter.py b/app/app/rate_limiter.py index dac3da6..5637be9 100644 --- a/app/app/rate_limiter.py +++ b/app/app/rate_limiter.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Optional +import newrelic.agent import redis.exceptions import werkzeug.exceptions from limits.storage import RedisStorage @@ -23,9 +24,15 @@ def check_bucket_limit( # Calculate current bucket time bucket_id = int(datetime.utcnow().timestamp()) % bucket_seconds bucket_lock_name = f"bl:{lock_name}:{bucket_id}" + if not lock_redis: + return try: value = lock_redis.incr(bucket_lock_name, bucket_seconds) if value > max_hits: + newrelic.agent.record_custom_event( + "BucketRateLimit", + {"lock_name": lock_name, "bucket_seconds": bucket_seconds}, + ) raise werkzeug.exceptions.TooManyRequests() - except redis.exceptions.RedisError: + except (redis.exceptions.RedisError, AttributeError): log.e("Cannot connect to redis") diff --git a/app/templates/header.html b/app/templates/header.html index 2db8c19..4877606 100644 --- a/app/templates/header.html +++ b/app/templates/header.html @@ -89,86 +89,87 @@ Github repo - - + + + - {% else %} - - {% endif %} - {% if current_user.should_show_upgrade_button() %} - - - {% endif %} - + {% else %} + + {% endif %} + {% if current_user.should_show_upgrade_button() %} + + + {% endif %} + - - - + + + -
-
-
-
- {% include "menu.html" %} +
+
+
+
+
+ {% include "menu.html" %} -
+