Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
7ea45d6f5d | |||
6d24db50bd | |||
88f270c6a1 | |||
0962b1cf29 |
@ -36,6 +36,7 @@ steps:
|
|||||||
status:
|
status:
|
||||||
- success
|
- success
|
||||||
- failure
|
- failure
|
||||||
|
- killed
|
||||||
settings:
|
settings:
|
||||||
webhook:
|
webhook:
|
||||||
from_secret: slack_webhook
|
from_secret: slack_webhook
|
||||||
|
@ -30,7 +30,7 @@ class ChangeEmailForm(FlaskForm):
|
|||||||
@dashboard_bp.route("/mailbox/<int:mailbox_id>/", methods=["GET", "POST"])
|
@dashboard_bp.route("/mailbox/<int:mailbox_id>/", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def mailbox_detail_route(mailbox_id):
|
def mailbox_detail_route(mailbox_id):
|
||||||
mailbox = Mailbox.get(mailbox_id)
|
mailbox: Mailbox = Mailbox.get(mailbox_id)
|
||||||
if not mailbox or mailbox.user_id != current_user.id:
|
if not mailbox or mailbox.user_id != current_user.id:
|
||||||
flash("You cannot see this page", "warning")
|
flash("You cannot see this page", "warning")
|
||||||
return redirect(url_for("dashboard.index"))
|
return redirect(url_for("dashboard.index"))
|
||||||
@ -144,6 +144,15 @@ def mailbox_detail_route(mailbox_id):
|
|||||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if mailbox.is_proton():
|
||||||
|
flash(
|
||||||
|
"Enabling PGP for a Proton Mail mailbox is redundant and does not add any security benefit",
|
||||||
|
"info",
|
||||||
|
)
|
||||||
|
return redirect(
|
||||||
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
|
)
|
||||||
|
|
||||||
mailbox.pgp_public_key = request.form.get("pgp")
|
mailbox.pgp_public_key = request.form.get("pgp")
|
||||||
try:
|
try:
|
||||||
mailbox.pgp_finger_print = load_public_key_and_check(
|
mailbox.pgp_finger_print = load_public_key_and_check(
|
||||||
@ -182,25 +191,16 @@ def mailbox_detail_route(mailbox_id):
|
|||||||
)
|
)
|
||||||
elif request.form.get("form-name") == "generic-subject":
|
elif request.form.get("form-name") == "generic-subject":
|
||||||
if request.form.get("action") == "save":
|
if request.form.get("action") == "save":
|
||||||
if not mailbox.pgp_enabled():
|
|
||||||
flash(
|
|
||||||
"Generic subject can only be used on PGP-enabled mailbox",
|
|
||||||
"error",
|
|
||||||
)
|
|
||||||
return redirect(
|
|
||||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
mailbox.generic_subject = request.form.get("generic-subject")
|
mailbox.generic_subject = request.form.get("generic-subject")
|
||||||
Session.commit()
|
Session.commit()
|
||||||
flash("Generic subject for PGP-encrypted email is enabled", "success")
|
flash("Generic subject is enabled", "success")
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
)
|
)
|
||||||
elif request.form.get("action") == "remove":
|
elif request.form.get("action") == "remove":
|
||||||
mailbox.generic_subject = None
|
mailbox.generic_subject = None
|
||||||
Session.commit()
|
Session.commit()
|
||||||
flash("Generic subject for PGP-encrypted email is disabled", "success")
|
flash("Generic subject is disabled", "success")
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
url_for("dashboard.mailbox_detail_route", mailbox_id=mailbox_id)
|
||||||
)
|
)
|
||||||
|
@ -34,7 +34,7 @@ def get_cname_record(hostname) -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def get_mx_domains(hostname) -> [(int, str)]:
|
def get_mx_domains(hostname) -> [(int, str)]:
|
||||||
"""return list of (priority, domain name).
|
"""return list of (priority, domain name) sorted by priority (lowest priority first)
|
||||||
domain name ends with a "." at the end.
|
domain name ends with a "." at the end.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -50,7 +50,7 @@ def get_mx_domains(hostname) -> [(int, str)]:
|
|||||||
|
|
||||||
ret.append((int(parts[0]), parts[1]))
|
ret.append((int(parts[0]), parts[1]))
|
||||||
|
|
||||||
return ret
|
return sorted(ret, key=lambda prio_domain: prio_domain[0])
|
||||||
|
|
||||||
|
|
||||||
_include_spf = "include:"
|
_include_spf = "include:"
|
||||||
|
@ -121,3 +121,10 @@ class AccountAlreadyLinkedToAnotherUserException(LinkException):
|
|||||||
class AccountIsUsingAliasAsEmail(LinkException):
|
class AccountIsUsingAliasAsEmail(LinkException):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Your account has an alias as it's email address")
|
super().__init__("Your account has an alias as it's email address")
|
||||||
|
|
||||||
|
|
||||||
|
class ProtonAccountNotVerified(LinkException):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
"The Proton account you are trying to use has not been verified"
|
||||||
|
)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import urllib
|
import urllib
|
||||||
|
from email.header import Header
|
||||||
from email.message import Message
|
from email.message import Message
|
||||||
|
|
||||||
from app.email import headers
|
from app.email import headers
|
||||||
@ -33,6 +34,8 @@ class UnsubscribeGenerator:
|
|||||||
if not unsubscribe_data:
|
if not unsubscribe_data:
|
||||||
LOG.info("Email has no unsubscribe header")
|
LOG.info("Email has no unsubscribe header")
|
||||||
return message
|
return message
|
||||||
|
if isinstance(unsubscribe_data, Header):
|
||||||
|
unsubscribe_data = str(unsubscribe_data.encode)
|
||||||
raw_methods = [method.strip() for method in unsubscribe_data.split(",")]
|
raw_methods = [method.strip() for method in unsubscribe_data.split(",")]
|
||||||
mailto_unsubs = None
|
mailto_unsubs = None
|
||||||
other_unsubs = []
|
other_unsubs = []
|
||||||
|
@ -30,6 +30,8 @@ from sqlalchemy_utils import ArrowType
|
|||||||
from app import config
|
from app import config
|
||||||
from app import s3
|
from app import s3
|
||||||
from app.db import Session
|
from app.db import Session
|
||||||
|
from app.dns_utils import get_mx_domains
|
||||||
|
|
||||||
from app.errors import (
|
from app.errors import (
|
||||||
AliasInTrashError,
|
AliasInTrashError,
|
||||||
DirectoryInTrashError,
|
DirectoryInTrashError,
|
||||||
@ -2569,6 +2571,27 @@ class Mailbox(Base, ModelMixin):
|
|||||||
+ Alias.filter_by(mailbox_id=self.id).count()
|
+ Alias.filter_by(mailbox_id=self.id).count()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_proton(self) -> bool:
|
||||||
|
if (
|
||||||
|
self.email.endswith("@proton.me")
|
||||||
|
or self.email.endswith("@protonmail.com")
|
||||||
|
or self.email.endswith("@protonmail.ch")
|
||||||
|
or self.email.endswith("@pm.me")
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
from app.email_utils import get_email_local_part
|
||||||
|
|
||||||
|
mx_domains: [(int, str)] = get_mx_domains(get_email_local_part(self.email))
|
||||||
|
# Proton is the first domain
|
||||||
|
if mx_domains and mx_domains[0][1] in (
|
||||||
|
"mail.protonmail.ch.",
|
||||||
|
"mailsec.protonmail.ch.",
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, obj_id):
|
def delete(cls, obj_id):
|
||||||
mailbox: Mailbox = cls.get(obj_id)
|
mailbox: Mailbox = cls.get(obj_id)
|
||||||
|
@ -7,11 +7,12 @@ from typing import Optional
|
|||||||
|
|
||||||
from app.account_linking import SLPlan, SLPlanType
|
from app.account_linking import SLPlan, SLPlanType
|
||||||
from app.config import PROTON_EXTRA_HEADER_NAME, PROTON_EXTRA_HEADER_VALUE
|
from app.config import PROTON_EXTRA_HEADER_NAME, PROTON_EXTRA_HEADER_VALUE
|
||||||
|
from app.errors import ProtonAccountNotVerified
|
||||||
from app.log import LOG
|
from app.log import LOG
|
||||||
|
|
||||||
_APP_VERSION = "OauthClient_1.0.0"
|
_APP_VERSION = "OauthClient_1.0.0"
|
||||||
|
|
||||||
PROTON_ERROR_CODE_NOT_EXISTS = 2501
|
PROTON_ERROR_CODE_HV_NEEDED = 9001
|
||||||
|
|
||||||
PLAN_FREE = 1
|
PLAN_FREE = 1
|
||||||
PLAN_PREMIUM = 2
|
PLAN_PREMIUM = 2
|
||||||
@ -57,6 +58,15 @@ def convert_access_token(access_token_response: str) -> AccessCredentials:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_response_not_ok(status: int, body: dict, text: str) -> Exception:
|
||||||
|
if status == HTTPStatus.UNPROCESSABLE_ENTITY:
|
||||||
|
res_code = body.get("Code")
|
||||||
|
if res_code == PROTON_ERROR_CODE_HV_NEEDED:
|
||||||
|
return ProtonAccountNotVerified()
|
||||||
|
|
||||||
|
return Exception(f"Unexpected status code. Wanted 200 and got {status}: " + text)
|
||||||
|
|
||||||
|
|
||||||
class ProtonClient(ABC):
|
class ProtonClient(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_user(self) -> Optional[UserInformation]:
|
def get_user(self) -> Optional[UserInformation]:
|
||||||
@ -124,11 +134,11 @@ class HttpProtonClient(ProtonClient):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def __validate_response(res: Response) -> dict:
|
def __validate_response(res: Response) -> dict:
|
||||||
status = res.status_code
|
status = res.status_code
|
||||||
if status != HTTPStatus.OK:
|
|
||||||
raise Exception(
|
|
||||||
f"Unexpected status code. Wanted 200 and got {status}: " + res.text
|
|
||||||
)
|
|
||||||
as_json = res.json()
|
as_json = res.json()
|
||||||
|
if status != HTTPStatus.OK:
|
||||||
|
raise HttpProtonClient.__handle_response_not_ok(
|
||||||
|
status=status, body=as_json, text=res.text
|
||||||
|
)
|
||||||
res_code = as_json.get("Code")
|
res_code = as_json.get("Code")
|
||||||
if not res_code or res_code != 1000:
|
if not res_code or res_code != 1000:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
@ -878,21 +878,22 @@ def forward_email_to_mailbox(
|
|||||||
headers_to_keep.append(headers.AUTHENTICATION_RESULTS)
|
headers_to_keep.append(headers.AUTHENTICATION_RESULTS)
|
||||||
delete_all_headers_except(msg, headers_to_keep)
|
delete_all_headers_except(msg, headers_to_keep)
|
||||||
|
|
||||||
|
if mailbox.generic_subject:
|
||||||
|
LOG.d("Use a generic subject for %s", mailbox)
|
||||||
|
orig_subject = msg[headers.SUBJECT]
|
||||||
|
orig_subject = get_header_unicode(orig_subject)
|
||||||
|
add_or_replace_header(msg, "Subject", mailbox.generic_subject)
|
||||||
|
sender = msg[headers.FROM]
|
||||||
|
sender = get_header_unicode(sender)
|
||||||
|
msg = add_header(
|
||||||
|
msg,
|
||||||
|
f"""Forwarded by SimpleLogin to {alias.email} from "{sender}" with "{orig_subject}" as subject""",
|
||||||
|
f"""Forwarded by SimpleLogin to {alias.email} from "{sender}" with <b>{orig_subject}</b> as subject""",
|
||||||
|
)
|
||||||
|
|
||||||
# create PGP email if needed
|
# create PGP email if needed
|
||||||
if mailbox.pgp_enabled() and user.is_premium() and not alias.disable_pgp:
|
if mailbox.pgp_enabled() and user.is_premium() and not alias.disable_pgp:
|
||||||
LOG.d("Encrypt message using mailbox %s", mailbox)
|
LOG.d("Encrypt message using mailbox %s", mailbox)
|
||||||
if mailbox.generic_subject:
|
|
||||||
LOG.d("Use a generic subject for %s", mailbox)
|
|
||||||
orig_subject = msg[headers.SUBJECT]
|
|
||||||
orig_subject = get_header_unicode(orig_subject)
|
|
||||||
add_or_replace_header(msg, "Subject", mailbox.generic_subject)
|
|
||||||
sender = msg[headers.FROM]
|
|
||||||
sender = get_header_unicode(sender)
|
|
||||||
msg = add_header(
|
|
||||||
msg,
|
|
||||||
f"""Forwarded by SimpleLogin to {alias.email} from "{sender}" with "{orig_subject}" as subject""",
|
|
||||||
f"""Forwarded by SimpleLogin to {alias.email} from "{sender}" with <b>{orig_subject}</b> as subject""",
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
msg = prepare_pgp_message(
|
msg = prepare_pgp_message(
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<a href="{{ 'mailto:' + contact.website_send_to() }}"
|
<a href="{{ 'mailto:' + contact.website_send_to() }}"
|
||||||
|
target="_blank"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title="You can click on this to open your email client. Or use the copy button 👉"
|
title="You can click on this to open your email client. Or use the copy button 👉"
|
||||||
class="font-weight-bold">
|
class="font-weight-bold">
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
{% if scope == "email" %}
|
{% if scope == "email" %}
|
||||||
|
|
||||||
Email:
|
Email:
|
||||||
<a href="mailto:{{ val }}">{{ val }}</a>
|
<a href="mailto:{{ val }}" target="_blank">{{ val }}</a>
|
||||||
{% elif scope == "name" %}
|
{% elif scope == "name" %}
|
||||||
Name: {{ val }}
|
Name: {{ val }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -71,177 +71,181 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- END Change email -->
|
<!-- END Change email -->
|
||||||
{% if mailbox.pgp_finger_print and not mailbox.disable_pgp and current_user.include_sender_in_reverse_alias %}
|
<!-- Not show PGP option for Proton mailbox -->
|
||||||
|
{% if mailbox.is_proton() and not mailbox.pgp_enabled() %}
|
||||||
|
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
Email headers like <span class="italic">From, To, Subject</span> aren't encrypted by PGP.
|
As an email is always encrypted at rest in Proton Mail, having SimpleLogin also encrypt your email is redundant and does not add any security benefit.
|
||||||
Currently, your reverse alias includes the sender address.
|
<br>
|
||||||
You can disable this on <a href="/dashboard/setting#sender-in-ra">Settings</a>.
|
The PGP option on SimpleLogin is instead useful for when your mailbox provider isn't encrypted by default like Gmail, Outlook, etc.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card">
|
<div class="{% if mailbox.is_proton() and not mailbox.pgp_enabled() %}
|
||||||
<div class="card-body">
|
disabled-content{% endif %}">
|
||||||
<div class="card-title">
|
{% if mailbox.pgp_finger_print and not mailbox.disable_pgp and current_user.include_sender_in_reverse_alias and not mailbox.is_proton() %}
|
||||||
<div class="d-flex">
|
|
||||||
Pretty Good Privacy (PGP)
|
<div class="alert alert-info">
|
||||||
|
Email headers like <span class="italic">From, To, Subject</span> aren't encrypted by PGP.
|
||||||
|
Currently, your reverse alias includes the sender address.
|
||||||
|
You can disable this on <a href="/dashboard/setting#sender-in-ra">Settings</a>.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">
|
||||||
|
<div class="d-flex">
|
||||||
|
Pretty Good Privacy (PGP)
|
||||||
|
{% if mailbox.pgp_finger_print %}
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{{ csrf_form.csrf_token }}
|
||||||
|
<input type="hidden" name="form-name" value="toggle-pgp">
|
||||||
|
<label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if mailbox.disable_pgp %}
|
||||||
|
title="Enable PGP" {% else %} title="Disable PGP" {% endif %}>
|
||||||
|
<input type="checkbox" class="custom-switch-input" name="pgp-enabled" {{ "" if mailbox.disable_pgp else "checked" }}>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="small-text mt-1">
|
||||||
|
By importing your PGP Public Key into SimpleLogin, all emails sent to {{ mailbox.email }} are
|
||||||
|
<b>encrypted</b> with your key.
|
||||||
|
<br />
|
||||||
|
{% if PGP_SIGNER %}All forwarded emails will be signed with <b>{{ PGP_SIGNER }}</b>.{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if not current_user.is_premium() %}
|
||||||
|
|
||||||
|
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
|
||||||
|
{% endif %}
|
||||||
|
<form method="post">
|
||||||
|
{{ csrf_form.csrf_token }}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">PGP Public Key</label>
|
||||||
|
<textarea name="pgp" {% if not current_user.is_premium() %} disabled {% endif %} class="form-control" rows=10 id="pgp-public-key" placeholder="(Drag and drop or paste your pgp public key here) -----BEGIN PGP PUBLIC KEY BLOCK-----">{{ mailbox.pgp_public_key or "" }}</textarea>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="form-name" value="pgp">
|
||||||
|
<button class="btn btn-primary" name="action" {% if not current_user.is_premium() %}
|
||||||
|
disabled {% endif %} value="save">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
{% if mailbox.pgp_finger_print %}
|
{% if mailbox.pgp_finger_print %}
|
||||||
|
|
||||||
<form method="post">
|
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
||||||
{{ csrf_form.csrf_token }}
|
|
||||||
<input type="hidden" name="form-name" value="toggle-pgp">
|
|
||||||
<label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if mailbox.disable_pgp %}
|
|
||||||
title="Enable PGP" {% else %} title="Disable PGP" {% endif %}>
|
|
||||||
<input type="checkbox" class="custom-switch-input" name="pgp-enabled" {{ "" if mailbox.disable_pgp else "checked" }}>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</form>
|
||||||
<div class="small-text mt-1">
|
|
||||||
By importing your PGP Public Key into SimpleLogin, all emails sent to {{ mailbox.email }} are
|
|
||||||
<b>encrypted</b> with your key.
|
|
||||||
<br />
|
|
||||||
{% if PGP_SIGNER %}All forwarded emails will be signed with <b>{{ PGP_SIGNER }}</b>.{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% if not current_user.is_premium() %}
|
|
||||||
|
|
||||||
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post">
|
|
||||||
{{ csrf_form.csrf_token }}
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">PGP Public Key</label>
|
|
||||||
<textarea name="pgp" {% if not current_user.is_premium() %} disabled {% endif %} class="form-control" rows=10 id="pgp-public-key" placeholder="(Drag and drop or paste your pgp public key here) -----BEGIN PGP PUBLIC KEY BLOCK-----">{{ mailbox.pgp_public_key or "" }}</textarea>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="form-name" value="pgp">
|
|
||||||
<button class="btn btn-primary" name="action" {% if not current_user.is_premium() %}
|
|
||||||
disabled {% endif %} value="save">
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
{% if mailbox.pgp_finger_print %}
|
|
||||||
|
|
||||||
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card" {% if not mailbox.pgp_enabled() %}
|
<div class="card" id="generic-subject">
|
||||||
disabled {% endif %}>
|
<form method="post" action="#generic-subject">
|
||||||
<form method="post">
|
|
||||||
{{ csrf_form.csrf_token }}
|
{{ csrf_form.csrf_token }}
|
||||||
<input type="hidden" name="form-name" value="generic-subject">
|
<input type="hidden" name="form-name" value="generic-subject">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
Hide email subject when PGP is enabled
|
Hide email subject
|
||||||
<div class="small-text mt-1">
|
<div class="small-text mt-1">
|
||||||
When PGP is enabled, you can choose to use a <b>generic</b> subject for the forwarded emails.
|
The original subject will be added to the email body and all forwarded emails will have the generic subject.
|
||||||
The original subject is then added into the email body.
|
|
||||||
<br />
|
<br />
|
||||||
As PGP does not encrypt the email subject and the email subject might contain sensitive information,
|
This option is often used when PGP is enabled.
|
||||||
this option will allow a further protection of your email content.
|
As PGP does not encrypt the email subject, it allows a further protection of your email content.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-info">
|
|
||||||
As the email is encrypted, a subject like "Email for you"
|
|
||||||
will probably be rejected by your mailbox since it sounds like a spam.
|
|
||||||
<br />
|
|
||||||
Something like "Encrypted Email" would work much better :).
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Generic Subject</label>
|
<label class="form-label">Generic Subject</label>
|
||||||
<input name="generic-subject" {% if not mailbox.pgp_enabled() %}
|
<input name="generic-subject"
|
||||||
disabled {% endif %} class="form-control" maxlength="78" placeholder="Generic Subject" value="{{ mailbox.generic_subject or "" }}">
|
class="form-control"
|
||||||
</div>
|
maxlength="78"
|
||||||
<button class="btn btn-primary" name="action" {% if not mailbox.pgp_enabled() %}
|
placeholder="Generic Subject"
|
||||||
disabled {% endif %} value="save">
|
value="{{ mailbox.generic_subject or "" }}">
|
||||||
Save
|
</div>
|
||||||
</button>
|
<button class="btn btn-primary" name="action" value="save">Save</button>
|
||||||
{% if mailbox.generic_subject %}
|
{% if mailbox.generic_subject %}
|
||||||
|
|
||||||
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<h2 class="h4">Advanced Options</h2>
|
||||||
|
{% if spf_available %}
|
||||||
|
|
||||||
|
<div class="card" id="spf">
|
||||||
|
<form method="post">
|
||||||
|
{{ csrf_form.csrf_token }}
|
||||||
|
<input type="hidden" name="form-name" value="force-spf">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title">
|
||||||
|
Enforce SPF
|
||||||
|
<div class="small-text">
|
||||||
|
To avoid email-spoofing, SimpleLogin blocks email that
|
||||||
|
<em data-toggle="tooltip"
|
||||||
|
title="Email that has your mailbox as envelope-sender address">seems</em> to come from your
|
||||||
|
mailbox
|
||||||
|
but sent from <em data-toggle="tooltip"
|
||||||
|
title="IP Address that is not known by your mailbox email service">unknown</em>
|
||||||
|
IP address.
|
||||||
|
<br />
|
||||||
|
Only turn off this option if you know what you're doing :).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if mailbox.force_spf %}
|
||||||
|
title="Disable SPF enforcement" {% else %} title="Enable SPF enforcement" {% endif %}>
|
||||||
|
<input type="checkbox" name="spf-status" class="custom-switch-input" {{ "checked" if mailbox.force_spf else "" }}>
|
||||||
|
<span class="custom-switch-indicator"></span>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
{% endif %}
|
||||||
<h2 class="h4">Advanced Options</h2>
|
<div class="card" id="authorized-address">
|
||||||
{% if spf_available %}
|
<div class="card-body">
|
||||||
|
<div class="card-title">
|
||||||
<div class="card" id="spf">
|
Authorized addresses
|
||||||
<form method="post">
|
<div class="small-text">
|
||||||
{{ csrf_form.csrf_token }}
|
Emails sent from these addresses to a <b>reverse-alias</b> are considered as being sent
|
||||||
<input type="hidden" name="form-name" value="force-spf">
|
from {{ mailbox.email }}
|
||||||
<div class="card-body">
|
|
||||||
<div class="card-title">
|
|
||||||
Enforce SPF
|
|
||||||
<div class="small-text">
|
|
||||||
To avoid email-spoofing, SimpleLogin blocks email that
|
|
||||||
<em data-toggle="tooltip"
|
|
||||||
title="Email that has your mailbox as envelope-sender address">seems</em> to come from your
|
|
||||||
mailbox
|
|
||||||
but sent from <em data-toggle="tooltip"
|
|
||||||
title="IP Address that is not known by your mailbox email service">unknown</em>
|
|
||||||
IP address.
|
|
||||||
<br />
|
|
||||||
Only turn off this option if you know what you're doing :).
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if mailbox.force_spf %}
|
|
||||||
title="Disable SPF enforcement" {% else %} title="Enable SPF enforcement" {% endif %}>
|
|
||||||
<input type="checkbox" name="spf-status" class="custom-switch-input" {{ "checked" if mailbox.force_spf else "" }}>
|
|
||||||
<span class="custom-switch-indicator"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="card" id="authorized-address">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="card-title">
|
|
||||||
Authorized addresses
|
|
||||||
<div class="small-text">
|
|
||||||
Emails sent from these addresses to a <b>reverse-alias</b> are considered as being sent
|
|
||||||
from {{ mailbox.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% if mailbox.authorized_addresses | length == 0 %}
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<ul>
|
|
||||||
{% for authorized_address in mailbox.authorized_addresses %}
|
|
||||||
|
|
||||||
<li>
|
|
||||||
{{ authorized_address.email }}
|
|
||||||
<form method="post" action="#authorized-address" style="display: inline">
|
|
||||||
{{ csrf_form.csrf_token }}
|
|
||||||
<input type="hidden" name="form-name" value="delete-authorized-address">
|
|
||||||
<input type="hidden"
|
|
||||||
name="authorized-address-id"
|
|
||||||
value="{{ authorized_address.id }}">
|
|
||||||
<input type="submit" class="btn btn-sm btn-outline-warning" value="Delete">
|
|
||||||
</form>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post" action="#authorized-address" class="form-inline">
|
|
||||||
{{ csrf_form.csrf_token }}
|
|
||||||
<input type="hidden" name="form-name" value="add-authorized-address">
|
|
||||||
<input type="email" name="email" size="50" class="form-control" required>
|
|
||||||
<input type="submit" class="btn btn-primary" value="Add">
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% if mailbox.authorized_addresses | length == 0 %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<ul>
|
||||||
|
{% for authorized_address in mailbox.authorized_addresses %}
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{{ authorized_address.email }}
|
||||||
|
<form method="post" action="#authorized-address" style="display: inline">
|
||||||
|
{{ csrf_form.csrf_token }}
|
||||||
|
<input type="hidden" name="form-name" value="delete-authorized-address">
|
||||||
|
<input type="hidden"
|
||||||
|
name="authorized-address-id"
|
||||||
|
value="{{ authorized_address.id }}">
|
||||||
|
<input type="submit" class="btn btn-sm btn-outline-warning" value="Delete">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<form method="post" action="#authorized-address" class="form-inline">
|
||||||
|
{{ csrf_form.csrf_token }}
|
||||||
|
<input type="hidden" name="form-name" value="add-authorized-address">
|
||||||
|
<input type="email" name="email" size="50" class="form-control" required>
|
||||||
|
<input type="submit" class="btn btn-primary" value="Add">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
</div>
|
||||||
{% block script %}
|
{% endblock %}
|
||||||
<script src="/static/js/utils/drag-drop-into-text.js"></script>
|
{% block script %}
|
||||||
<script>
|
<script src="/static/js/utils/drag-drop-into-text.js"></script>
|
||||||
|
<script>
|
||||||
$(".custom-switch-input").change(function (e) {
|
$(".custom-switch-input").change(function (e) {
|
||||||
$(this).closest("form").submit();
|
$(this).closest("form").submit();
|
||||||
});
|
});
|
||||||
enableDragDropForPGPKeys('#pgp-public-key');
|
enableDragDropForPGPKeys('#pgp-public-key');
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -207,7 +207,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="h3">Proton plan</div>
|
<div class="h3">Proton plan</div>
|
||||||
<div class="h3 my-3">Starts at $11.99 / month</div>
|
<div class="h3 my-3">Starts at $12.99 / month</div>
|
||||||
<div class="text-center mt-4 mb-6">
|
<div class="text-center mt-4 mb-6">
|
||||||
<a class="btn btn-lg btn-outline-primary w-100"
|
<a class="btn btn-lg btn-outline-primary w-100"
|
||||||
role="button"
|
role="button"
|
||||||
@ -225,10 +225,6 @@
|
|||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
500 GB storage
|
500 GB storage
|
||||||
</li>
|
</li>
|
||||||
<li class="d-flex">
|
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
|
||||||
15 email addresses
|
|
||||||
</li>
|
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
Unlimited folders, labels, and filters
|
Unlimited folders, labels, and filters
|
||||||
@ -239,11 +235,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
15 email addresses
|
25 calendars
|
||||||
</li>
|
|
||||||
<li class="d-flex">
|
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
|
||||||
20 Calendars
|
|
||||||
</li>
|
</li>
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
@ -376,10 +368,6 @@
|
|||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
500 GB storage
|
500 GB storage
|
||||||
</li>
|
</li>
|
||||||
<li class="d-flex">
|
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
|
||||||
15 email addresses/aliases
|
|
||||||
</li>
|
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
Unlimited folders, labels, and filters
|
Unlimited folders, labels, and filters
|
||||||
@ -390,11 +378,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
15 email addresses/aliases
|
25 calendars
|
||||||
</li>
|
|
||||||
<li class="d-flex">
|
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
|
||||||
20 Calendars
|
|
||||||
</li>
|
</li>
|
||||||
<li class="d-flex">
|
<li class="d-flex">
|
||||||
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
<i class="fe fe-check text-success mr-2 mt-1" aria-hidden="true"></i>
|
||||||
@ -478,7 +462,7 @@
|
|||||||
</a>, which currently supports Bitcoin, Bitcoin Cash, DAI, ApeCoin, Dogecoin, Ethereum, Litecoin, SHIBA INU, Tether and USD Coin.
|
</a>, which currently supports Bitcoin, Bitcoin Cash, DAI, ApeCoin, Dogecoin, Ethereum, Litecoin, SHIBA INU, Tether and USD Coin.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
In the future, we are going to support Monero as well. In the meantime, please send us an email at <a href="mailto:support@simplelogin.zendesk.com">support@simplelogin.zendesk.com</a> if you want to use this cryptocurrency.
|
In the future, we are going to support Monero as well. In the meantime, please send us an email at <a href="mailto:support@simplelogin.zendesk.com" target="_blank">support@simplelogin.zendesk.com</a> if you want to use this cryptocurrency.
|
||||||
</p>
|
</p>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<a class="btn btn-outline-primary text-center"
|
<a class="btn btn-outline-primary text-center"
|
||||||
@ -645,7 +629,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
Please send us an email at <a href="mailto:support@simplelogin.zendesk.com">support@simplelogin.zendesk.com</a> for more info.
|
Please send us an email at <a href="mailto:support@simplelogin.zendesk.com" target="_blank">support@simplelogin.zendesk.com</a> for more info.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We used to offer free premium accounts for students but this program ended at June 17 2021. Please note this doesn't affect existing accounts who have already benefited from the program or requests sent before this date.
|
We used to offer free premium accounts for students but this program ended at June 17 2021. Please note this doesn't affect existing accounts who have already benefited from the program or requests sent before this date.
|
||||||
@ -708,7 +692,7 @@
|
|||||||
data-parent="#pricing-faq">
|
data-parent="#pricing-faq">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>
|
<p>
|
||||||
No we don't have a family plan but offer 30% reduction for additional subscriptions. Please contact us at <a href="mailto:support@simplelogin.zendesk.com">support@simplelogin.zendesk.com</a> for more information.
|
No we don't have a family plan but offer 30% reduction for additional subscriptions. Please contact us at <a href="mailto:support@simplelogin.zendesk.com" target="_blank">support@simplelogin.zendesk.com</a> for more information.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
For every user who <b>upgrades</b> and stays with us at least 3 months, you'll get $5 :).
|
For every user who <b>upgrades</b> and stays with us at least 3 months, you'll get $5 :).
|
||||||
<br />
|
<br />
|
||||||
The payout can be initiated any time, just send us an email at
|
The payout can be initiated any time, just send us an email at
|
||||||
<a href="mailto:hi@simplelogin.io">hi@simplelogin.io</a>
|
<a href="mailto:hi@simplelogin.io" target="_blank">hi@simplelogin.io</a>
|
||||||
when you want to receive the payout.
|
when you want to receive the payout.
|
||||||
</div>
|
</div>
|
||||||
{% if referrals|length == 0 %}
|
{% if referrals|length == 0 %}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<h1 class="h3">Block alias</h1>
|
<h1 class="h3">Block alias</h1>
|
||||||
<p>
|
<p>
|
||||||
You are about to block the alias
|
You are about to block the alias
|
||||||
<a href="mailto:{{ alias }}">{{ alias }}</a>
|
<a href="mailto:{{ alias }}" target="_blank">{{ alias }}</a>
|
||||||
</p>
|
</p>
|
||||||
<p>After this, you will stop receiving all emails sent to this alias, please confirm.</p>
|
<p>After this, you will stop receiving all emails sent to this alias, please confirm.</p>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<img src="{{ user_info[scope.value] }}" class="avatar">
|
<img src="{{ user_info[scope.value] }}" class="avatar">
|
||||||
{% elif scope == Scope.EMAIL %}
|
{% elif scope == Scope.EMAIL %}
|
||||||
{{ scope.value }}:
|
{{ scope.value }}:
|
||||||
<a href="mailto:{{ user_info[scope.value] }}">{{ user_info[scope.value] }}</a>
|
<a href="mailto:{{ user_info[scope.value] }}" target="_blank">{{ user_info[scope.value] }}</a>
|
||||||
{% elif scope == Scope.NAME %}
|
{% elif scope == Scope.NAME %}
|
||||||
{{ scope.value }}: <b>{{ user_info[scope.value] }}</b>
|
{{ scope.value }}: <b>{{ user_info[scope.value] }}</b>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from app.errors import ProtonAccountNotVerified
|
||||||
from app.proton import proton_client
|
from app.proton import proton_client
|
||||||
|
|
||||||
|
|
||||||
@ -19,3 +21,30 @@ def test_convert_access_token_not_containing_invalid_length():
|
|||||||
for case in cases:
|
for case in cases:
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
proton_client.convert_access_token(case)
|
proton_client.convert_access_token(case)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_response_not_ok_account_not_verified():
|
||||||
|
res = proton_client.handle_response_not_ok(
|
||||||
|
status=HTTPStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
body={"Code": proton_client.PROTON_ERROR_CODE_HV_NEEDED},
|
||||||
|
text="",
|
||||||
|
)
|
||||||
|
assert isinstance(res, ProtonAccountNotVerified)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_response_unprocessable_entity_not_account_not_verified():
|
||||||
|
error_text = "some error text"
|
||||||
|
res = proton_client.handle_response_not_ok(
|
||||||
|
status=HTTPStatus.UNPROCESSABLE_ENTITY, body={"Code": 4567}, text=error_text
|
||||||
|
)
|
||||||
|
assert error_text in res.args[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_response_not_ok_unknown_error():
|
||||||
|
error_text = "some error text"
|
||||||
|
res = proton_client.handle_response_not_ok(
|
||||||
|
status=123,
|
||||||
|
body={"Code": proton_client.PROTON_ERROR_CODE_HV_NEEDED},
|
||||||
|
text=error_text,
|
||||||
|
)
|
||||||
|
assert error_text in res.args[0]
|
||||||
|
@ -71,7 +71,7 @@ def load_eml_file(
|
|||||||
if not template_values:
|
if not template_values:
|
||||||
template_values = {}
|
template_values = {}
|
||||||
rendered = template.render(**template_values)
|
rendered = template.render(**template_values)
|
||||||
return email.message_from_string(rendered)
|
return email.message_from_bytes(rendered.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def random_email() -> str:
|
def random_email() -> str:
|
||||||
|
Reference in New Issue
Block a user