4.38.2
All checks were successful
continuous-integration/drone/tag Build is passing

This commit is contained in:
MrMeeb 2024-02-06 12:00:07 +00:00
parent e9faf93878
commit 5b47bd1654
5 changed files with 119 additions and 74 deletions

View File

@ -214,6 +214,20 @@ class UserAdmin(SLModelView):
Session.commit() 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( @action(
"disable_otp_fido", "disable_otp_fido",
"Disable OTP & FIDO", "Disable OTP & FIDO",

View File

@ -17,9 +17,14 @@ from app.models import PlanEnum, AppleSubscription
_MONTHLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.monthly" _MONTHLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.monthly"
_YEARLY_PRODUCT_ID = "io.simplelogin.ios_app.subscription.premium.yearly" _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_MONTHLY_PRODUCT_ID = "io.simplelogin.macapp.subscription.premium.monthly"
_MACAPP_YEARLY_PRODUCT_ID = "io.simplelogin.macapp.subscription.premium.yearly" _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 # Apple API URL
_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt" _SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt"
_PROD_URL = "https://buy.itunes.apple.com/verifyReceipt" _PROD_URL = "https://buy.itunes.apple.com/verifyReceipt"
@ -263,7 +268,11 @@ def apple_update_notification():
plan = ( plan = (
PlanEnum.monthly PlanEnum.monthly
if transaction["product_id"] 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 else PlanEnum.yearly
) )
@ -517,7 +526,11 @@ def verify_receipt(receipt_data, user, password) -> Optional[AppleSubscription]:
plan = ( plan = (
PlanEnum.monthly PlanEnum.monthly
if latest_transaction["product_id"] 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 else PlanEnum.yearly
) )

View File

@ -235,6 +235,7 @@ class AuditLogActionEnum(EnumE):
download_provider_complaint = 8 download_provider_complaint = 8
disable_user = 9 disable_user = 9
enable_user = 10 enable_user = 10
stop_trial = 11
class Phase(EnumE): 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 @classmethod
def disable_otp_fido( def disable_otp_fido(
cls, admin_user_id: int, user_id: int, had_otp: bool, had_fido: bool cls, admin_user_id: int, user_id: int, had_otp: bool, had_fido: bool

View File

@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
import newrelic.agent
import redis.exceptions import redis.exceptions
import werkzeug.exceptions import werkzeug.exceptions
from limits.storage import RedisStorage from limits.storage import RedisStorage
@ -23,9 +24,15 @@ def check_bucket_limit(
# Calculate current bucket time # Calculate current bucket time
bucket_id = int(datetime.utcnow().timestamp()) % bucket_seconds bucket_id = int(datetime.utcnow().timestamp()) % bucket_seconds
bucket_lock_name = f"bl:{lock_name}:{bucket_id}" bucket_lock_name = f"bl:{lock_name}:{bucket_id}"
if not lock_redis:
return
try: try:
value = lock_redis.incr(bucket_lock_name, bucket_seconds) value = lock_redis.incr(bucket_lock_name, bucket_seconds)
if value > max_hits: if value > max_hits:
newrelic.agent.record_custom_event(
"BucketRateLimit",
{"lock_name": lock_name, "bucket_seconds": bucket_seconds},
)
raise werkzeug.exceptions.TooManyRequests() raise werkzeug.exceptions.TooManyRequests()
except redis.exceptions.RedisError: except (redis.exceptions.RedisError, AttributeError):
log.e("Cannot connect to redis") log.e("Cannot connect to redis")

View File

@ -89,86 +89,87 @@
Github repo Github repo
<i class="fa fa-external-link" aria-hidden="true"></i> <i class="fa fa-external-link" aria-hidden="true"></i>
</a> </a>
<div class="dropdown-item"> </div>
<a href="https://forum.simplelogin.io" <div class="dropdown-item">
target="_blank" <a href="https://forum.simplelogin.io"
rel="noopener noreferrer"> target="_blank"
Forum rel="noopener noreferrer">
<i class="fa fa-external-link" aria-hidden="true"></i> Forum
</a> <i class="fa fa-external-link" aria-hidden="true"></i>
</div> </a>
<div class="dropdown-item"> </div>
<a href="/dashboard/support">Support</a> <div class="dropdown-item">
</div> <a href="/dashboard/support">Support</a>
</div> </div>
</div> </div>
{% else %} </div>
<div class="nav-item"> {% else %}
<a href="https://simplelogin.io/docs/" <div class="nav-item">
target="_blank" <a href="https://simplelogin.io/docs/"
rel="noopener noreferrer"> target="_blank"
Docs rel="noopener noreferrer">
<i class="fa fa-external-link" aria-hidden="true"></i> Docs
</a> <i class="fa fa-external-link" aria-hidden="true"></i>
</div> </a>
{% endif %} </div>
{% if current_user.should_show_upgrade_button() %} {% endif %}
{% if current_user.should_show_upgrade_button() %}
<div class="nav-item">
<a href="{{ url_for('dashboard.pricing') }}" <div class="nav-item">
class="btn btn-sm btn-outline-primary">Upgrade</a> <a href="{{ url_for('dashboard.pricing') }}"
</div> class="btn btn-sm btn-outline-primary">Upgrade</a>
{% endif %} </div>
<div class="dropdown"> {% endif %}
<a href="#" class="nav-link pr-0 leading-none" data-toggle="dropdown"> <div class="dropdown">
{% if current_user.profile_picture_id %} <a href="#" class="nav-link pr-0 leading-none" data-toggle="dropdown">
{% if current_user.profile_picture_id %}
<span class="avatar"
style="background-image: url('{{ current_user.profile_picture_url() }}')"></span> <span class="avatar"
{% else %} style="background-image: url('{{ current_user.profile_picture_url() }}')"></span>
<span class="avatar avatar-blue">{{ current_user.get_name_initial() or "👻" }}</span> {% else %}
{% endif %} <span class="avatar avatar-blue">{{ current_user.get_name_initial() or "👻" }}</span>
<span class="ml-2 d-none d-lg-block"> {% endif %}
<span class="text-default text-break">{{ current_user.name or current_user.email }}</span> <span class="ml-2 d-none d-lg-block">
{% if current_user.in_trial() %} <span class="text-default text-break">{{ current_user.name or current_user.email }}</span>
{% if current_user.in_trial() %}
<small class="text-success d-block mt-1"
data-toggle="tooltip" <small class="text-success d-block mt-1"
title="When you signed up, you have a free 7-day Premium trial. After that your account will automatically be downgraded to the Free plan. During the trial, the only limit is you can't create more than {{ MAX_NB_EMAIL_FREE_PLAN }} aliases."> data-toggle="tooltip"
Premium expires {{ current_user.trial_end|dt }} title="When you signed up, you have a free 7-day Premium trial. After that your account will automatically be downgraded to the Free plan. During the trial, the only limit is you can't create more than {{ MAX_NB_EMAIL_FREE_PLAN }} aliases.">
<i class="fe fe-info"></i> Premium expires {{ current_user.trial_end|dt }}
</small> <i class="fe fe-info"></i>
{% elif current_user.is_premium() %} </small>
<small class="text-success d-block mt-1">Premium</small> {% elif current_user.is_premium() %}
{% endif %} <small class="text-success d-block mt-1">Premium</small>
</span> {% endif %}
</span>
</a>
<div class="dropdown-menu dropdown-menu-right dropdown-menu-arrow">
<a class="dropdown-item mb-3" href="{{ url_for('dashboard.api_key') }}">
<i class="dropdown-icon fa fa-key"></i> API Keys
</a>
<a class="dropdown-item" href="{{ url_for('auth.logout') }}">
<i class="dropdown-icon fe fe-log-out"></i> Sign out
</a> </a>
<div class="dropdown-menu dropdown-menu-right dropdown-menu-arrow">
<a class="dropdown-item mb-3" href="{{ url_for('dashboard.api_key') }}">
<i class="dropdown-icon fa fa-key"></i> API Keys
</a>
<a class="dropdown-item" href="{{ url_for('auth.logout') }}">
<i class="dropdown-icon fe fe-log-out"></i> Sign out
</a>
</div>
</div> </div>
</div> </div>
<a href="#"
class="header-toggler d-lg-none ml-3 ml-lg-0"
data-toggle="collapse"
data-target="#headerMenuCollapse">
<span class="header-toggler-icon"></span>
</a>
</div> </div>
<a href="#"
class="header-toggler d-lg-none ml-3 ml-lg-0"
data-toggle="collapse"
data-target="#headerMenuCollapse">
<span class="header-toggler-icon"></span>
</a>
</div> </div>
</div> </div>
<div class="header collapse d-lg-flex p-0" id="headerMenuCollapse"> </div>
<div class="container"> <div class="header collapse d-lg-flex p-0" id="headerMenuCollapse">
<div class="row align-items-center"> <div class="container">
<div class="col-lg order-lg-first"> <div class="row align-items-center">
{% include "menu.html" %} <div class="col-lg order-lg-first">
{% include "menu.html" %}
</div>
</div> </div>
</div> </div>
</div> </div>
</div>