Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
c872d43c3d | |||
3e6867bc17 |
@ -84,7 +84,7 @@ For email gurus, we have chosen 1024 key length instead of 2048 for DNS simplici
|
||||
|
||||
### DNS
|
||||
|
||||
Please note that DNS changes could take up to 24 hours to propagate. In practice, it's a lot faster though (~1 minute or so in our test). In DNS setup, we usually use domain with a trailing dot (`.`) at the end to to force using absolute domain.
|
||||
Please note that DNS changes could take up to 24 hours to propagate. In practice, it's a lot faster though (~1 minute or so in our test). In DNS setup, we usually use domain with a trailing dot (`.`) at the end to force using absolute domain.
|
||||
|
||||
|
||||
#### MX record
|
||||
|
@ -6,6 +6,7 @@ from typing import Optional
|
||||
import arrow
|
||||
from arrow import Arrow
|
||||
from newrelic import agent
|
||||
from psycopg2.errors import UniqueViolation
|
||||
from sqlalchemy import or_
|
||||
|
||||
from app.db import Session
|
||||
@ -160,15 +161,55 @@ class ClientMergeStrategy(ABC):
|
||||
|
||||
class NewUserStrategy(ClientMergeStrategy):
|
||||
def process(self) -> LinkResult:
|
||||
# Will create a new SL User with a random password
|
||||
canonical_email = canonicalize_email(self.link_request.email)
|
||||
new_user = User.create(
|
||||
email=canonical_email,
|
||||
name=self.link_request.name,
|
||||
password=random_string(20),
|
||||
activated=True,
|
||||
from_partner=self.link_request.from_partner,
|
||||
try:
|
||||
# Will create a new SL User with a random password
|
||||
new_user = User.create(
|
||||
email=canonical_email,
|
||||
name=self.link_request.name,
|
||||
password=random_string(20),
|
||||
activated=True,
|
||||
from_partner=self.link_request.from_partner,
|
||||
)
|
||||
self.create_partner_user(new_user)
|
||||
Session.commit()
|
||||
|
||||
if not new_user.created_by_partner:
|
||||
send_welcome_email(new_user)
|
||||
|
||||
agent.record_custom_event(
|
||||
"PartnerUserCreation", {"partner": self.partner.name}
|
||||
)
|
||||
|
||||
return LinkResult(
|
||||
user=new_user,
|
||||
strategy=self.__class__.__name__,
|
||||
)
|
||||
except UniqueViolation:
|
||||
return self.create_missing_link(canonical_email)
|
||||
|
||||
def create_missing_link(self, canonical_email: str):
|
||||
# If there's a unique key violation due to race conditions try to create only the partner if needed
|
||||
partner_user = PartnerUser.get_by(
|
||||
external_user_id=self.link_request.external_user_id,
|
||||
partner_id=self.partner.id,
|
||||
)
|
||||
if partner_user is None:
|
||||
# Get the user by canonical email and if not by normal email
|
||||
user = User.get_by(email=canonical_email) or User.get_by(
|
||||
email=self.link_request.email
|
||||
)
|
||||
if not user:
|
||||
raise RuntimeError(
|
||||
"Tried to create only partner on UniqueViolation but cannot find the user"
|
||||
)
|
||||
partner_user = self.create_partner_user(user)
|
||||
Session.commit()
|
||||
return LinkResult(
|
||||
user=partner_user.user, strategy=ExistingUnlinkedUserStrategy.__name__
|
||||
)
|
||||
|
||||
def create_partner_user(self, new_user: User):
|
||||
partner_user = create_partner_user(
|
||||
user=new_user,
|
||||
partner_id=self.partner.id,
|
||||
@ -182,17 +223,7 @@ class NewUserStrategy(ClientMergeStrategy):
|
||||
partner_user,
|
||||
self.link_request.plan,
|
||||
)
|
||||
Session.commit()
|
||||
|
||||
if not new_user.created_by_partner:
|
||||
send_welcome_email(new_user)
|
||||
|
||||
agent.record_custom_event("PartnerUserCreation", {"partner": self.partner.name})
|
||||
|
||||
return LinkResult(
|
||||
user=new_user,
|
||||
strategy=self.__class__.__name__,
|
||||
)
|
||||
return partner_user
|
||||
|
||||
|
||||
class ExistingUnlinkedUserStrategy(ClientMergeStrategy):
|
||||
|
@ -58,7 +58,7 @@ def verify_prefix_suffix(
|
||||
|
||||
# alias_domain must be either one of user custom domains or built-in domains
|
||||
if alias_domain not in user.available_alias_domains(alias_options=alias_options):
|
||||
LOG.e("wrong alias suffix %s, user %s", alias_suffix, user)
|
||||
LOG.i("wrong alias suffix %s, user %s", alias_suffix, user)
|
||||
return False
|
||||
|
||||
# SimpleLogin domain case:
|
||||
@ -75,17 +75,17 @@ def verify_prefix_suffix(
|
||||
and not config.DISABLE_ALIAS_SUFFIX
|
||||
):
|
||||
if not alias_domain_prefix.startswith("."):
|
||||
LOG.e("User %s submits a wrong alias suffix %s", user, alias_suffix)
|
||||
LOG.i("User %s submits a wrong alias suffix %s", user, alias_suffix)
|
||||
return False
|
||||
|
||||
else:
|
||||
if alias_domain not in user_custom_domains:
|
||||
if not config.DISABLE_ALIAS_SUFFIX:
|
||||
LOG.e("wrong alias suffix %s, user %s", alias_suffix, user)
|
||||
LOG.i("wrong alias suffix %s, user %s", alias_suffix, user)
|
||||
return False
|
||||
|
||||
if alias_domain not in available_sl_domains:
|
||||
LOG.e("wrong alias suffix %s, user %s", alias_suffix, user)
|
||||
LOG.i("wrong alias suffix %s, user %s", alias_suffix, user)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -153,7 +153,8 @@ def new_custom_alias_v3():
|
||||
if not isinstance(data, dict):
|
||||
return jsonify(error="request body does not follow the required format"), 400
|
||||
|
||||
alias_prefix = data.get("alias_prefix", "").strip().lower().replace(" ", "")
|
||||
alias_prefix_data = data.get("alias_prefix", "") or ""
|
||||
alias_prefix = alias_prefix_data.strip().lower().replace(" ", "")
|
||||
signed_suffix = data.get("signed_suffix", "") or ""
|
||||
signed_suffix = signed_suffix.strip()
|
||||
|
||||
|
@ -227,7 +227,10 @@ def alias_contact_manager(alias_id):
|
||||
|
||||
page = 0
|
||||
if request.args.get("page"):
|
||||
page = int(request.args.get("page"))
|
||||
try:
|
||||
page = int(request.args.get("page"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
query = request.args.get("query") or ""
|
||||
|
||||
|
@ -71,7 +71,10 @@ def index():
|
||||
|
||||
page = 0
|
||||
if request.args.get("page"):
|
||||
page = int(request.args.get("page"))
|
||||
try:
|
||||
page = int(request.args.get("page"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
highlight_alias_id = None
|
||||
if request.args.get("highlight_alias_id"):
|
||||
|
@ -43,7 +43,10 @@ def notification_route(notification_id):
|
||||
def notifications_route():
|
||||
page = 0
|
||||
if request.args.get("page"):
|
||||
page = int(request.args.get("page"))
|
||||
try:
|
||||
page = int(request.args.get("page"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
notifications = (
|
||||
Notification.filter_by(user_id=current_user.id)
|
||||
|
@ -565,6 +565,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle):
|
||||
"ix_users_activated_trial_end_lifetime", activated, trial_end, lifetime
|
||||
),
|
||||
sa.Index("ix_users_delete_on", delete_on),
|
||||
sa.Index("ix_users_default_mailbox_id", default_mailbox_id),
|
||||
)
|
||||
|
||||
@property
|
||||
@ -1666,6 +1667,11 @@ class Alias(Base, ModelMixin):
|
||||
custom_domain = Alias.get_custom_domain(email)
|
||||
if custom_domain:
|
||||
new_alias.custom_domain_id = custom_domain.id
|
||||
else:
|
||||
custom_domain = CustomDomain.get(kw["custom_domain_id"])
|
||||
# If it comes from a custom domain created from partner. Mark it as created from partner
|
||||
if custom_domain is not None and custom_domain.partner_id is not None:
|
||||
new_alias.flags = (new_alias.flags or 0) | Alias.FLAG_PARTNER_CREATED
|
||||
|
||||
Session.add(new_alias)
|
||||
DailyMetric.get_or_create_today_metric().nb_alias += 1
|
||||
@ -2069,7 +2075,11 @@ class Contact(Base, ModelMixin):
|
||||
|
||||
class EmailLog(Base, ModelMixin):
|
||||
__tablename__ = "email_log"
|
||||
__table_args__ = (Index("ix_email_log_created_at", "created_at"),)
|
||||
__table_args__ = (
|
||||
Index("ix_email_log_created_at", "created_at"),
|
||||
Index("ix_email_log_mailbox_id", "mailbox_id"),
|
||||
Index("ix_email_log_bounced_mailbox_id", "bounced_mailbox_id"),
|
||||
)
|
||||
|
||||
user_id = sa.Column(
|
||||
sa.ForeignKey(User.id, ondelete="cascade"), nullable=False, index=True
|
||||
@ -2747,7 +2757,10 @@ class Mailbox(Base, ModelMixin):
|
||||
|
||||
generic_subject = sa.Column(sa.String(78), nullable=True)
|
||||
|
||||
__table_args__ = (sa.UniqueConstraint("user_id", "email", name="uq_mailbox_user"),)
|
||||
__table_args__ = (
|
||||
sa.UniqueConstraint("user_id", "email", name="uq_mailbox_user"),
|
||||
sa.Index("ix_mailbox_pgp_finger_print", "pgp_finger_print"),
|
||||
)
|
||||
|
||||
user = orm.relationship(User, foreign_keys=[user_id])
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
"""add missing indices on user and mailbox
|
||||
|
||||
Revision ID: 842ac670096e
|
||||
Revises: bc9aa210efa3
|
||||
Create Date: 2024-11-13 15:55:28.798506
|
||||
|
||||
"""
|
||||
import sqlalchemy_utils
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '842ac670096e'
|
||||
down_revision = 'bc9aa210efa3'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.get_context().autocommit_block():
|
||||
op.create_index('ix_mailbox_pgp_finger_print', 'mailbox', ['pgp_finger_print'], unique=False)
|
||||
op.create_index('ix_users_default_mailbox_id', 'users', ['default_mailbox_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.get_context().autocommit_block():
|
||||
op.drop_index('ix_users_default_mailbox_id', table_name='users')
|
||||
op.drop_index('ix_mailbox_pgp_finger_print', table_name='mailbox')
|
@ -0,0 +1,29 @@
|
||||
"""add missing indices on email log
|
||||
|
||||
Revision ID: 12274da2299f
|
||||
Revises: 842ac670096e
|
||||
Create Date: 2024-11-14 10:27:20.371191
|
||||
|
||||
"""
|
||||
import sqlalchemy_utils
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '12274da2299f'
|
||||
down_revision = '842ac670096e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.get_context().autocommit_block():
|
||||
op.create_index('ix_email_log_bounced_mailbox_id', 'email_log', ['bounced_mailbox_id'], unique=False)
|
||||
op.create_index('ix_email_log_mailbox_id', 'email_log', ['mailbox_id'], unique=False)
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.get_context().autocommit_block():
|
||||
op.drop_index('ix_email_log_mailbox_id', table_name='email_log')
|
||||
op.drop_index('ix_email_log_bounced_mailbox_id', table_name='email_log')
|
@ -1,286 +1,295 @@
|
||||
{% extends 'admin/master.html' %}
|
||||
|
||||
{% macro show_user(user) -%}
|
||||
<h4>User {{ user.email }} with ID {{ user.id }}.</h4>
|
||||
{% set pu = helper.partner_user(user) %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">User ID</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Verified</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Paid</th>
|
||||
<th scope="col">Premium</th>
|
||||
<th>Subscription</th>
|
||||
<th>Created At</th>
|
||||
<th>Updated At</th>
|
||||
<th>Connected with Proton account</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td><a href="?email={{ user.email }}">{{ user.email }}</a></td>
|
||||
{% if user.activated %}
|
||||
<td class="text-success">Activated</td>
|
||||
{% else %}
|
||||
<td class="text-warning">Pending</td>
|
||||
{% endif %}
|
||||
{% if user.disabled %}
|
||||
<td class="text-danger">Disabled</td>
|
||||
{% else %}
|
||||
<td class="text-success">Enabled</td>
|
||||
{% endif %}
|
||||
<td>{{ "yes" if user.is_paid() else "No" }}</td>
|
||||
<td>{{ "yes" if user.is_premium() else "No" }}</td>
|
||||
<td>{{ user.get_active_subscription() }}</td>
|
||||
<td>{{ user.created_at }}</td>
|
||||
<td>{{ user.updated_at }}</td>
|
||||
{% if pu %}
|
||||
<h4>User {{ user.email }} with ID {{ user.id }}.</h4>
|
||||
{% set pu = helper.partner_user(user) %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">User ID</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Verified</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Paid</th>
|
||||
<th scope="col">Premium</th>
|
||||
<th>Subscription</th>
|
||||
<th>Created At</th>
|
||||
<th>Updated At</th>
|
||||
<th>Connected with Proton account</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ user.id }}</td>
|
||||
<td>
|
||||
<a href="?email={{ user.email }}">{{ user.email }}</a>
|
||||
</td>
|
||||
{% if user.activated %}
|
||||
|
||||
<td><a href="?email={{ pu.partner_email }}">{{ pu.partner_email }}</a></td>
|
||||
{% else %}
|
||||
<td>No</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<td class="text-success">Activated</td>
|
||||
{% else %}
|
||||
<td class="text-warning">Pending</td>
|
||||
{% endif %}
|
||||
{% if user.disabled %}
|
||||
|
||||
<td class="text-danger">Disabled</td>
|
||||
{% else %}
|
||||
<td class="text-success">Enabled</td>
|
||||
{% endif %}
|
||||
<td>{{ "yes" if user.is_paid() else "No" }}</td>
|
||||
<td>{{ "yes" if user.is_premium() else "No" }}</td>
|
||||
<td>{{ user.get_active_subscription() }}</td>
|
||||
<td>{{ user.created_at }}</td>
|
||||
<td>{{ user.updated_at }}</td>
|
||||
{% if pu %}
|
||||
|
||||
<td>
|
||||
<a href="?email={{ pu.partner_email }}">{{ pu.partner_email }}</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>No</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{%- endmacro %}
|
||||
{% macro list_mailboxes(message, mbox_count, mboxes) %}
|
||||
<h4>
|
||||
{{ mbox_count }} {{ message }}.
|
||||
{% if mbox_count>10 %}Showing only the last 10.{% endif %}
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<h4>
|
||||
{{ mbox_count }} {{ message }}.
|
||||
{% if mbox_count>10 %}Showing only the last 10.{% endif %}
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mailbox ID</th>
|
||||
<th>Email</th>
|
||||
<th>Verified</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mailbox in mboxes %}
|
||||
<tr>
|
||||
<th>Mailbox ID</th>
|
||||
<th>Email</th>
|
||||
<th>Verified</th>
|
||||
<th>Created At</th>
|
||||
<td>{{ mailbox.id }}</td>
|
||||
<td>
|
||||
<a href="?email={{ mailbox.email }}">{{ mailbox.email }}</a>
|
||||
</td>
|
||||
<td>{{ "Yes" if mailbox.verified else "No" }}</td>
|
||||
<td>{{ mailbox.created_at }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mailbox in mboxes %}
|
||||
|
||||
<tr>
|
||||
<td>{{ mailbox.id }}</td>
|
||||
<td><a href="?email={{ mailbox.email }}">{{ mailbox.email }}</a></td>
|
||||
<td>{{ "Yes" if mailbox.verified else "No" }}</td>
|
||||
<td>
|
||||
{{ mailbox.created_at }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% macro list_alias(alias_count, aliases) %}
|
||||
<h4>
|
||||
{{ alias_count }} Aliases found.
|
||||
{% if alias_count>10 %}Showing only the last 10.{% endif %}
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<h4>
|
||||
{{ alias_count }} Aliases found.
|
||||
{% if alias_count>10 %}Showing only the last 10.{% endif %}
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alias ID</th>
|
||||
<th>Email</th>
|
||||
<th>Enabled</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for alias in aliases %}
|
||||
|
||||
<tr>
|
||||
<th>
|
||||
Alias ID
|
||||
</th>
|
||||
<th>
|
||||
Email
|
||||
</th>
|
||||
<th>
|
||||
Enabled
|
||||
</th>
|
||||
<th>
|
||||
Created At
|
||||
</th>
|
||||
<td>{{ alias.id }}</td>
|
||||
<td>
|
||||
<a href="?email={{ alias.email }}">{{ alias.email }}</a>
|
||||
</td>
|
||||
<td>{{ "Yes" if alias.enabled else "No" }}</td>
|
||||
<td>{{ alias.created_at }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for alias in aliases %}
|
||||
<tr>
|
||||
<td>{{ alias.id }}</td>
|
||||
<td><a href="?email={{ alias.email }}">{{ alias.email }}</a></td>
|
||||
<td>{{ "Yes" if alias.enabled else "No" }}</td>
|
||||
<td>{{ alias.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% macro show_deleted_alias(deleted_alias) -%}
|
||||
<h4>Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}.</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Deleted Alias ID</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Deleted At</th>
|
||||
<th scope="col">Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ deleted_alias.id }}</td>
|
||||
<td>{{ deleted_alias.email }}</td>
|
||||
<td>{{ deleted_alias.created_at }}</td>
|
||||
<td>{{ deleted_alias.reason }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}.</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Deleted Alias ID</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Deleted At</th>
|
||||
<th scope="col">Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ deleted_alias.id }}</td>
|
||||
<td>{{ deleted_alias.email }}</td>
|
||||
<td>{{ deleted_alias.created_at }}</td>
|
||||
<td>{{ deleted_alias.reason }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{%- endmacro %}
|
||||
{% macro show_domain_deleted_alias(dom_deleted_alias) -%}
|
||||
<h4>
|
||||
Domain Deleted Alias {{ dom_deleted_alias.email }} with ID {{ dom_deleted_alias.id }} for
|
||||
domain {{ dom_deleted_alias.domain.domain }}
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Deleted Alias ID</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Domain</th>
|
||||
<th scope="col">Domain ID</th>
|
||||
<th scope="col">Domain owner user ID</th>
|
||||
<th scope="col">Domain owner user email</th>
|
||||
<th scope="col">Deleted At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ dom_deleted_alias.id }}</td>
|
||||
<td>{{ dom_deleted_alias.email }}</td>
|
||||
<td>{{ dom_deleted_alias.domain.domain }}</td>
|
||||
<td>{{ dom_deleted_alias.domain.id }}</td>
|
||||
<td>{{ dom_deleted_alias.domain.user_id }}</td>
|
||||
<td>{{ dom_deleted_alias.created_at }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{ show_user(data.domain_deleted_alias.domain.user) }}
|
||||
<h4>
|
||||
Domain Deleted Alias {{ dom_deleted_alias.email }} with ID {{ dom_deleted_alias.id }} for
|
||||
domain {{ dom_deleted_alias.domain.domain }}
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Deleted Alias ID</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Domain</th>
|
||||
<th scope="col">Domain ID</th>
|
||||
<th scope="col">Domain owner user ID</th>
|
||||
<th scope="col">Domain owner user email</th>
|
||||
<th scope="col">Deleted At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ dom_deleted_alias.id }}</td>
|
||||
<td>{{ dom_deleted_alias.email }}</td>
|
||||
<td>{{ dom_deleted_alias.domain.domain }}</td>
|
||||
<td>{{ dom_deleted_alias.domain.id }}</td>
|
||||
<td>{{ dom_deleted_alias.domain.user_id }}</td>
|
||||
<td>{{ dom_deleted_alias.created_at }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{ show_user(data.domain_deleted_alias.domain.user) }}
|
||||
{%- endmacro %}
|
||||
{% macro list_alias_audit_log(alias_audit_log) %}
|
||||
<h4>Alias Audit Log</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<h4>Alias Audit Log</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Alias ID</th>
|
||||
<th>Alias Email</th>
|
||||
<th>Action</th>
|
||||
<th>Message</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in alias_audit_log %}
|
||||
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Alias ID</th>
|
||||
<th>Alias Email</th>
|
||||
<th>Action</th>
|
||||
<th>Message</th>
|
||||
<th>Time</th>
|
||||
<td>{{ entry.user_id }}</td>
|
||||
<td>{{ entry.alias_id }}</td>
|
||||
<td>
|
||||
<a href="?email={{ entry.alias_email }}">{{ entry.alias_email }}</a>
|
||||
</td>
|
||||
<td>{{ entry.action }}</td>
|
||||
<td>{{ entry.message }}</td>
|
||||
<td>{{ entry.created_at }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in alias_audit_log %}
|
||||
<tr>
|
||||
<td>{{ entry.user_id }}</td>
|
||||
<td>{{ entry.alias_id }}</td>
|
||||
<td><a href="?email={{ entry.alias_email }}">{{ entry.alias_email }}</a></td>
|
||||
<td>{{ entry.action }}</td>
|
||||
<td>{{ entry.message }}</td>
|
||||
<td>{{ entry.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% macro list_user_audit_log(user_audit_log) %}
|
||||
<h4>User Audit Log</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<h4>User Audit Log</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User email</th>
|
||||
<th>Action</th>
|
||||
<th>Message</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in user_audit_log %}
|
||||
|
||||
<tr>
|
||||
<th>User email</th>
|
||||
<th>Action</th>
|
||||
<th>Message</th>
|
||||
<th>Time</th>
|
||||
<td>
|
||||
<a href="?email={{ entry.user_email }}">{{ entry.user_email }}</a>
|
||||
</td>
|
||||
<td>{{ entry.action }}</td>
|
||||
<td>{{ entry.message }}</td>
|
||||
<td>{{ entry.created_at }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in user_audit_log %}
|
||||
<tr>
|
||||
<td><a href="?email={{ entry.user_email }}">{{ entry.user_email }}</a></td>
|
||||
<td>{{ entry.action }}</td>
|
||||
<td>{{ entry.message }}</td>
|
||||
<td>{{ entry.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% block body %}
|
||||
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<form method="get">
|
||||
<div class="form-group">
|
||||
<label for="email">Email to search:</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="email"
|
||||
value="{{ email or '' }}" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if data.no_match and email %}
|
||||
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3 alert alert-warning"
|
||||
role="alert">No user, alias or mailbox found for {{ email }}</div>
|
||||
{% endif %}
|
||||
{% if data.alias %}
|
||||
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<form method="get">
|
||||
<div class="form-group">
|
||||
<label for="email">Email to search:</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="email"
|
||||
value="{{ email or '' }}"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<h3 class="mb-3">Found Alias {{ data.alias.email }}</h3>
|
||||
{{ list_alias(1,[data.alias]) }}
|
||||
{{ list_alias_audit_log(data.alias_audit_log) }}
|
||||
{{ list_mailboxes("Mailboxes for alias", helper.alias_mailbox_count(data.alias) , helper.alias_mailboxes(data.alias)) }}
|
||||
{{ show_user(data.alias.user) }}
|
||||
</div>
|
||||
{% if data.no_match and email %}
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3 alert alert-warning"
|
||||
role="alert">No user, alias or mailbox found for {{ email }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if data.user %}
|
||||
|
||||
{% if data.alias %}
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found Alias {{ data.alias.email }}</h3>
|
||||
{{ list_alias(1,[data.alias]) }}
|
||||
{{ list_alias_audit_log(data.alias_audit_log) }}
|
||||
{{ list_mailboxes("Mailboxes for alias", helper.alias_mailbox_count(data.alias), helper.alias_mailboxes(data.alias)) }}
|
||||
{{ show_user(data.alias.user) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found User {{ data.user.email }}</h3>
|
||||
{{ show_user(data.user) }}
|
||||
{{ list_mailboxes("Mailboxes for user", helper.mailbox_count(data.user) , helper.mailbox_list(data.user) ) }}
|
||||
{{ list_alias(helper.alias_count(data.user) ,helper.alias_list(data.user)) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.user_audit_log %}
|
||||
|
||||
{% if data.user %}
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found User {{ data.user.email }}</h3>
|
||||
{{ show_user(data.user) }}
|
||||
{{ list_mailboxes("Mailboxes for user", helper.mailbox_count(data.user) , helper.mailbox_list(data.user) ) }}
|
||||
{{ list_alias(helper.alias_count(data.user) ,helper.alias_list(data.user)) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.user_audit_log %}
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Audit log entries for user {{ data.query }}</h3>
|
||||
{{ list_user_audit_log(data.user_audit_log) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.mailbox_count > 10 %}
|
||||
<h3>Found more than 10 mailboxes for {{ email }}. Showing the last 10</h3>
|
||||
{% elif data.mailbox_count > 0 %}
|
||||
<h3>Found {{ data.mailbox_count }} mailbox(es) for {{ email }}</h3>
|
||||
{% endif %}
|
||||
{% for mailbox in data.mailbox %}
|
||||
<div class="border border-dark border-2 mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Audit log entries for user {{ data.query }}</h3>
|
||||
{{ list_user_audit_log(data.user_audit_log) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.mailbox_count > 10 %}
|
||||
|
||||
<div class="border border-dark mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found Mailbox {{ mailbox.email }}</h3>
|
||||
{{ list_mailboxes("Mailbox found", 1, [mailbox]) }}
|
||||
{{ show_user(mailbox.user) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if data.deleted_alias %}
|
||||
<h3>Found more than 10 mailboxes for {{ email }}. Showing the last 10</h3>
|
||||
{% elif data.mailbox_count > 0 %}
|
||||
<h3>Found {{ data.mailbox_count }} mailbox(es) for {{ email }}</h3>
|
||||
{% endif %}
|
||||
{% for mailbox in data.mailbox %}
|
||||
|
||||
<div class="border border-dark mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found DeletedAlias {{ data.deleted_alias.email }}</h3>
|
||||
{{ show_deleted_alias(data.deleted_alias) }}
|
||||
{{ list_alias_audit_log(data.deleted_alias_audit_log) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.domain_deleted_alias %}
|
||||
<div class="border border-dark mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found Mailbox {{ mailbox.email }}</h3>
|
||||
{{ list_mailboxes("Mailbox found", 1, [mailbox]) }}
|
||||
{{ show_user(mailbox.user) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if data.deleted_alias %}
|
||||
|
||||
<div class="border border-dark mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found DomainDeletedAlias {{ data.domain_deleted_alias.email }}</h3>
|
||||
{{ show_domain_deleted_alias(data.domain_deleted_alias) }}
|
||||
{{ list_alias_audit_log(data.domain_deleted_alias_audit_log) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="border border-dark mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found DeletedAlias {{ data.deleted_alias.email }}</h3>
|
||||
{{ show_deleted_alias(data.deleted_alias) }}
|
||||
{{ list_alias_audit_log(data.deleted_alias_audit_log) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if data.domain_deleted_alias %}
|
||||
|
||||
<div class="border border-dark mt-1 mb-2 p-3">
|
||||
<h3 class="mb-3">Found DomainDeletedAlias {{ data.domain_deleted_alias.email }}</h3>
|
||||
{{ show_domain_deleted_alias(data.domain_deleted_alias) }}
|
||||
{{ list_alias_audit_log(data.domain_deleted_alias_audit_log) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -43,7 +43,7 @@
|
||||
You can change the plan at any moment.
|
||||
<br />
|
||||
Please note that the new billing cycle starts instantly
|
||||
i.e. you will be charged <b>immediately</b> the annual fee ($30) when switching from monthly plan or vice-versa
|
||||
i.e. you will be charged <b>immediately</b> the annual fee ($36) when switching from monthly plan or vice-versa
|
||||
<b>without pro rata computation </b>.
|
||||
<br />
|
||||
To change the plan you can also cancel the current one and subscribe a new one <b>by the end</b> of this plan.
|
||||
|
@ -94,4 +94,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -91,7 +91,6 @@
|
||||
<br />
|
||||
Some domain registrars (Namecheap, CloudFlare, etc) might also use <em>@</em> for the root domain.
|
||||
</div>
|
||||
|
||||
{% for record in expected_mx_records %}
|
||||
|
||||
<div class="mb-3 p-3 dns-record">
|
||||
@ -108,7 +107,6 @@
|
||||
data-clipboard-text="{{ record.domain }}">{{ record.domain }}</em>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<form method="post" action="#mx-form">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="check-mx">
|
||||
|
@ -22,7 +22,8 @@
|
||||
<p>Alternatively you can use your Proton credentials to ensure it's you.</p>
|
||||
</div>
|
||||
<a class="btn btn-primary btn-block mt-2 proton-button"
|
||||
href="{{ url_for('auth.proton_login', next=next) }}" style="max-width: 400px">
|
||||
href="{{ url_for('auth.proton_login', next=next) }}"
|
||||
style="max-width: 400px">
|
||||
<img class="mr-2" src="/static/images/proton.svg" />
|
||||
Authenticate with Proton
|
||||
</a>
|
||||
@ -38,4 +39,4 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -11,7 +11,7 @@
|
||||
<div>
|
||||
<a class="buy-with-crypto"
|
||||
data-custom="{{ current_user.id }}"
|
||||
href="{{ coinbase_url }}">Extend for 1 year - $30</a>
|
||||
href="{{ coinbase_url }}">Extend for 1 year - $36</a>
|
||||
<script src="https://commerce.coinbase.com/v1/checkout.js?version=201807"></script>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
|
@ -77,6 +77,11 @@
|
||||
<div class="text-center mx-md-auto mb-8 mt-6">
|
||||
<h1>Upgrade to unlock premium features</h1>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<span class="badge badge-success">new</span> SimpleLogin Premium now includes Proton Pass premium features.
|
||||
<a href="https://simplelogin.io/blog/sl-premium-including-pass-plus/"
|
||||
target="_blank">Learn more ↗</a>
|
||||
</div>
|
||||
{% if manual_sub %}
|
||||
|
||||
<div class="alert alert-info mt-0 mb-6">
|
||||
@ -306,7 +311,7 @@
|
||||
<div class="card-body">
|
||||
<div class="text-center">
|
||||
<div class="h3">SimpleLogin Premium</div>
|
||||
<div class="h3 my-3">$30 / year</div>
|
||||
<div class="h3 my-3">$36 / year</div>
|
||||
<div class="text-center mt-4 mb-6">
|
||||
<button class="btn btn-primary btn-lg w-100"
|
||||
onclick="upgradePaddle({{ PADDLE_YEARLY_PRODUCT_ID }})">Upgrade to Premium</button>
|
||||
@ -471,7 +476,7 @@
|
||||
rel="noopener noreferrer">
|
||||
Upgrade to Premium - cryptocurrency
|
||||
<br />
|
||||
$30 / year
|
||||
$36 / year
|
||||
<i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
from app.db import Session
|
||||
from app.models import Alias, Mailbox, AliasMailbox, User
|
||||
from app.models import Alias, Mailbox, AliasMailbox, User, CustomDomain
|
||||
from tests.utils import create_new_user, random_email
|
||||
|
||||
|
||||
@ -29,3 +29,23 @@ def test_alias_create_from_partner_flags_also_the_user():
|
||||
flush=True,
|
||||
)
|
||||
assert alias.user.flags & User.FLAG_CREATED_ALIAS_FROM_PARTNER > 0
|
||||
|
||||
|
||||
def test_alias_create_from_partner_domain_flags_the_alias():
|
||||
user = create_new_user()
|
||||
domain = CustomDomain.create(
|
||||
domain=random_email(),
|
||||
verified=True,
|
||||
user_id=user.id,
|
||||
partner_id=1,
|
||||
)
|
||||
Session.flush()
|
||||
email = random_email()
|
||||
alias = Alias.create(
|
||||
user_id=user.id,
|
||||
email=email,
|
||||
mailbox_id=user.default_mailbox_id,
|
||||
custom_domain_id=domain.id,
|
||||
flush=True,
|
||||
)
|
||||
assert alias.flags & Alias.FLAG_PARTNER_CREATED > 0
|
||||
|
@ -144,6 +144,21 @@ def test_login_case_from_web():
|
||||
assert audit_logs[0].action == UserAuditLogAction.LinkAccount.value
|
||||
|
||||
|
||||
def test_new_user_strategy_create_missing_link():
|
||||
email = random_email()
|
||||
user = User.create(email, commit=True)
|
||||
nus = NewUserStrategy(
|
||||
link_request=random_link_request(
|
||||
email=user.email, external_user_id=random_string(), from_partner=False
|
||||
),
|
||||
user=None,
|
||||
partner=get_proton_partner(),
|
||||
)
|
||||
result = nus.create_missing_link(user.email)
|
||||
assert result.user.id == user.id
|
||||
assert result.strategy == ExistingUnlinkedUserStrategy.__name__
|
||||
|
||||
|
||||
def test_get_strategy_existing_sl_user():
|
||||
email = random_email()
|
||||
user = User.create(email, commit=True)
|
||||
|
Reference in New Issue
Block a user