Merge branch 'master' into master

This commit is contained in:
Raymond Nook 2021-05-26 22:33:20 -07:00 committed by GitHub
commit 258d505cbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 652 additions and 110 deletions

View File

@ -236,7 +236,7 @@ If you already have a Postgres database in use, you can skip this section and ju
Run a Postgres Docker container as your Postgres database server. Make sure to replace `myuser` and `mypassword` with something more secret.
```bash
sudo docker run -d \
docker run -d \
--name sl-db \
-e POSTGRES_PASSWORD=mypassword \
-e POSTGRES_USER=myuser \
@ -251,7 +251,7 @@ sudo docker run -d \
To test whether the database operates correctly or not, run the following command:
```bash
sudo docker exec -it sl-db psql -U myuser simplelogin
docker exec -it sl-db psql -U myuser simplelogin
```
you should be logged in the postgres console. Type `exit` to exit postgres console.
@ -419,7 +419,7 @@ LOCAL_FILE_UPLOAD=1
Before running the webapp, you need to prepare the database by running the migration:
```bash
sudo docker run --rm \
docker run --rm \
--name sl-migration \
-v $(pwd)/sl:/sl \
-v $(pwd)/sl/upload:/code/static/upload \
@ -435,7 +435,7 @@ This command could take a while to download the `simplelogin/app` docker image.
Init data
```bash
sudo docker run --rm \
docker run --rm \
--name sl-init \
-v $(pwd)/sl:/sl \
-v $(pwd)/simplelogin.env:/code/.env \
@ -448,7 +448,7 @@ sudo docker run --rm \
Now, it's time to run the `webapp` container!
```bash
sudo docker run -d \
docker run -d \
--name sl-app \
-v $(pwd)/sl:/sl \
-v $(pwd)/sl/upload:/code/static/upload \
@ -464,7 +464,7 @@ sudo docker run -d \
Next run the `email handler`
```bash
sudo docker run -d \
docker run -d \
--name sl-email \
-v $(pwd)/sl:/sl \
-v $(pwd)/sl/upload:/code/static/upload \
@ -487,7 +487,7 @@ sudo apt-get install -y nginx
Then, create `/etc/nginx/sites-enabled/simplelogin` with the following lines:
```
```nginx
server {
server_name app.mydomain.com;

View File

@ -3,6 +3,7 @@ from typing import Optional
from sqlalchemy.exc import IntegrityError, DataError
from app.config import BOUNCE_PREFIX_FOR_REPLY_PHASE
from app.email_utils import (
get_email_domain_part,
send_cannot_create_directory_alias,
@ -29,6 +30,12 @@ from app.models import (
def try_auto_create(address: str) -> Optional[Alias]:
"""Try to auto-create the alias using directory or catch-all domain"""
if address.startswith(f"{BOUNCE_PREFIX_FOR_REPLY_PHASE}+"):
LOG.exception(
"alias %s can't start with %s", address, BOUNCE_PREFIX_FOR_REPLY_PHASE
)
return None
alias = try_auto_create_catch_all_domain(address)
if not alias:
alias = try_auto_create_directory(address)

View File

@ -27,7 +27,7 @@ def options():
existing: array of existing aliases
"""
LOG.warning("/alias/options is obsolete")
LOG.e("/alias/options is obsolete")
user = g.user
hostname = request.args.get("hostname")
@ -106,7 +106,7 @@ def options_v2():
"""
LOG.exception("/v2/alias/options is obsolete")
LOG.e("/v2/alias/options is obsolete")
user = g.user
hostname = request.args.get("hostname")
@ -186,7 +186,7 @@ def options_v3():
"""
LOG.warning("/v3/alias/options is obsolete")
LOG.e("/v3/alias/options is obsolete")
user = g.user
hostname = request.args.get("hostname")

View File

@ -44,7 +44,7 @@ def new_custom_alias():
409 if the alias already exists
"""
LOG.warning("/alias/custom/new is obsolete")
LOG.e("/alias/custom/new is obsolete")
user: User = g.user
if not user.can_create_new_alias():
LOG.d("user %s cannot create any custom alias", user)

View File

@ -75,6 +75,16 @@ BOUNCE_PREFIX = os.environ.get("BOUNCE_PREFIX") or "bounce+"
BOUNCE_SUFFIX = os.environ.get("BOUNCE_SUFFIX") or f"+@{EMAIL_DOMAIN}"
BOUNCE_EMAIL = BOUNCE_PREFIX + "{}" + BOUNCE_SUFFIX
# Used for VERP during reply phase. It's similar to BOUNCE_PREFIX.
# It's needed when sending emails from custom domain to respect DMARC.
# BOUNCE_PREFIX_FOR_REPLY_PHASE should never be used in any existing alias
# and can't be used for creating a new alias on custom domain
# Note BOUNCE_PREFIX_FOR_REPLY_PHASE doesn't have the trailing plus sign (+) as BOUNCE_PREFIX
BOUNCE_PREFIX_FOR_REPLY_PHASE = (
os.environ.get("BOUNCE_PREFIX_FOR_REPLY_PHASE") or "bounce_reply"
)
# VERP for transactional email: mail_from set to BOUNCE_PREFIX + email_log.id + BOUNCE_SUFFIX
TRANSACTIONAL_BOUNCE_PREFIX = (
os.environ.get("TRANSACTIONAL_BOUNCE_PREFIX") or "transactional+"
@ -376,3 +386,9 @@ ALIAS_LIMIT = os.environ.get("ALIAS_LIMIT") or "100/day;50/hour;5/minute"
ENABLE_SPAM_ASSASSIN = "ENABLE_SPAM_ASSASSIN" in os.environ
ALIAS_RANDOM_SUFFIX_LENGTH = int(os.environ.get("ALIAS_RAND_SUFFIX_LENGTH", 5))
try:
HIBP_SCAN_INTERVAL_DAYS = int(os.environ.get("HIBP_SCAN_INTERVAL_DAYS"))
except Exception:
HIBP_SCAN_INTERVAL_DAYS = 7
HIBP_API_KEYS = sl_getenv("HIBP_API_KEYS", list) or []

View File

@ -131,7 +131,7 @@
{% endfor %}
</select>
<button class="btn btn-lg btn-success mt-2">Create</button>
<button class="btn btn-primary mt-2">Create</button>
</form>
</div>
</div>

View File

@ -177,7 +177,7 @@
{% endfor %}
</select>
<button id="btn-create-directory" class="btn btn-lg btn-success mt-2">Create</button>
<button id="btn-create-directory" class="btn btn-primary mt-2">Create</button>
</form>
</div>

View File

@ -202,6 +202,13 @@
<span class="fa fa-heart" data-toggle="tooltip"
title="This alias added to favorite"></span>
{% endif %}
{% if alias.hibp_breaches | length > 0 %}
<a href="https://haveibeenpwned.com/account/{{ alias.email }}">
<span class="fa fa-warning text-danger" data-toggle="tooltip"
title="This alias was found in {{ alias.hibp_breaches | length }} data breaches. Check haveibeenpwned.com for more information."></span>
</a>
{% endif %}
</div>
<div class="col text-right">
<label class="custom-switch cursor"
@ -453,17 +460,19 @@
{% endfor %}
</div>
<!-- Only show pagination control if there are previous/next page -->
{% if page > 0 or not last_page %}
<div class="row">
<div class="col">
<nav aria-label="Alias navigation">
<ul class="pagination">
<li class="page-item">
<a class="btn btn-outline-secondary {% if page == 0 %}disabled{% endif %}"
<a class="btn btn-outline-primary {% if page == 0 %}disabled{% endif %}"
href="{{ url_for('dashboard.index', page=page-1, query=query, sort=sort, filter=filter) }}">
Previous</a>
</li>
<li class="page-item">
<a class="btn btn-outline-secondary {% if last_page %}disabled{% endif %}"
<a class="btn btn-outline-primary {% if last_page %}disabled{% endif %}"
href="{{ url_for('dashboard.index', page=page+1, query=query, sort=sort, filter=filter) }}">
Next</a>
</li>
@ -471,6 +480,7 @@
</nav>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -117,7 +117,7 @@
A mailbox can't be a disposable or forwarding email address.
</div>
<button class="btn btn-lg btn-success mt-2">Create</button>
<button class="btn btn-primary mt-2">Create</button>
</form>
</div>

View File

@ -3,7 +3,12 @@ from flask_login import login_required, current_user
from flask_wtf import FlaskForm
from wtforms import StringField, validators
from app.config import EMAIL_DOMAIN, ALIAS_DOMAINS, MAX_NB_DIRECTORY
from app.config import (
EMAIL_DOMAIN,
ALIAS_DOMAINS,
MAX_NB_DIRECTORY,
BOUNCE_PREFIX_FOR_REPLY_PHASE,
)
from app.dashboard.base import dashboard_bp
from app.extensions import db
from app.models import Directory, Mailbox, DirectoryMailbox
@ -126,6 +131,7 @@ def directory():
"bounces",
"bounce",
"transactional",
BOUNCE_PREFIX_FOR_REPLY_PHASE,
):
flash(
"this directory name is reserved, please choose another name",

View File

@ -6,6 +6,7 @@ import quopri
import random
import re
import time
from email.errors import HeaderParseError
from email.header import decode_header
from email.message import Message
from email.mime.multipart import MIMEMultipart
@ -369,7 +370,7 @@ def send_email_at_most_times(
return True
def get_email_local_part(address):
def get_email_local_part(address) -> str:
"""
Get the local part from email
ab@cd.com -> ab
@ -667,20 +668,24 @@ def parseaddr_unicode(addr) -> (str, str):
email = email.strip().lower()
if name:
name = name.strip()
decoded_string, charset = decode_header(name)[0]
if charset is not None:
try:
name = decoded_string.decode(charset)
except UnicodeDecodeError:
LOG.warning("Cannot decode addr name %s", name)
name = ""
except LookupError: # charset is unknown
LOG.warning(
"Cannot decode %s with %s, use utf-8", decoded_string, charset
)
name = decoded_string.decode("utf-8")
try:
decoded_string, charset = decode_header(name)[0]
except HeaderParseError: # fail in case
LOG.warning("Can't decode name %s", name)
else:
name = decoded_string
if charset is not None:
try:
name = decoded_string.decode(charset)
except UnicodeDecodeError:
LOG.warning("Cannot decode addr name %s", name)
name = ""
except LookupError: # charset is unknown
LOG.warning(
"Cannot decode %s with %s, use utf-8", decoded_string, charset
)
name = decoded_string.decode("utf-8")
else:
name = decoded_string
if type(name) == bytes:
name = name.decode()
@ -1163,6 +1168,14 @@ def sl_sendmail(
# smtp.send_message has UnicodeEncodeError
# encode message raw directly instead
LOG.d(
"Sendmail mail_from:%s, rcpt_to:%s, header_from:%s, header_to:%s, header_cc:%s",
from_addr,
to_addr,
msg["From"],
msg["To"],
msg["Cc"],
)
smtp.sendmail(
from_addr,
to_addr,

View File

@ -163,10 +163,20 @@ class AliasGeneratorEnum(EnumE):
uuid = 2 # aliases are generated based on uuid
class AliasSuffixEnum(EnumE):
word = 0 # Random word from dictionary file
random_string = 1 # Completely random string
class Hibp(db.Model, ModelMixin):
__tablename__ = "hibp"
name = db.Column(db.String(), nullable=False, unique=True, index=True)
breached_aliases = db.relationship("Alias", secondary="alias_hibp")
def __repr__(self):
return f"<HIBP Breach {self.id} {self.name}>"
class Fido(db.Model, ModelMixin):
__tablename__ = "fido"
@ -472,7 +482,7 @@ class User(db.Model, ModelMixin, UserMixin):
sub: Subscription = self.get_subscription()
if sub:
return "Paddle Subscription"
return f"Paddle Subscription {sub.subscription_id}"
apple_sub: AppleSubscription = AppleSubscription.get_by(user_id=self.id)
if apple_sub and apple_sub.is_valid():
@ -1067,6 +1077,10 @@ class Alias(db.Model, ModelMixin):
# used to transfer an alias to another user
transfer_token = db.Column(db.String(64), default=None, unique=True, nullable=True)
# have I been pwned
hibp_last_check = db.Column(ArrowType, default=None)
hibp_breaches = db.relationship("Hibp", secondary="alias_hibp")
user = db.relationship(User, foreign_keys=[user_id])
mailbox = db.relationship("Mailbox", lazy="joined")
@ -2020,6 +2034,22 @@ class AliasMailbox(db.Model, ModelMixin):
alias = db.relationship(Alias)
class AliasHibp(db.Model, ModelMixin):
__tablename__ = "alias_hibp"
__table_args__ = (db.UniqueConstraint("alias_id", "hibp_id", name="uq_alias_hibp"),)
alias_id = db.Column(db.Integer(), db.ForeignKey("alias.id", ondelete="cascade"))
hibp_id = db.Column(db.Integer(), db.ForeignKey("hibp.id", ondelete="cascade"))
alias = db.relationship(
"Alias", backref=db.backref("alias_hibp", cascade="all, delete-orphan")
)
hibp = db.relationship(
"Hibp", backref=db.backref("alias_hibp", cascade="all, delete-orphan")
)
class DirectoryMailbox(db.Model, ModelMixin):
__table_args__ = (
db.UniqueConstraint("directory_id", "mailbox_id", name="uq_directory_mailbox"),

121
cron.py
View File

@ -1,9 +1,12 @@
import argparse
import asyncio
import urllib.parse
from time import sleep
from typing import List, Tuple
import arrow
from sqlalchemy import func, desc
import requests
from sqlalchemy import func, desc, or_
from app import s3
from app.alias_utils import nb_email_log_for_mailbox
@ -15,6 +18,8 @@ from app.config import (
EMAIL_SERVERS_WITH_PRIORITY,
URL,
AlERT_WRONG_MX_RECORD_CUSTOM_DOMAIN,
HIBP_API_KEYS,
HIBP_SCAN_INTERVAL_DAYS,
)
from app.dns_utils import get_mx_domains
from app.email_utils import (
@ -50,6 +55,7 @@ from app.models import (
SLDomain,
DeletedAlias,
DomainDeletedAlias,
Hibp,
)
from app.utils import sanitize_email
from server import create_app
@ -757,6 +763,115 @@ def delete_old_monitoring():
LOG.d("delete monitoring records older than %s, nb row %s", max_time, nb_row)
async def _hibp_check(api_key, queue):
"""
Uses a single API key to check the queue as fast as possible.
This function to be ran simultaneously (multiple _hibp_check functions with different keys on the same queue) to make maximum use of multiple API keys.
"""
while True:
try:
alias_id = queue.get_nowait()
except asyncio.QueueEmpty:
return
alias = Alias.get(alias_id)
# an alias can be deleted in the meantime
if not alias:
return
LOG.d("Checking HIBP for %s", alias)
request_headers = {
"user-agent": "SimpleLogin",
"hibp-api-key": api_key,
}
r = requests.get(
f"https://haveibeenpwned.com/api/v3/breachedaccount/{urllib.parse.quote(alias.email)}",
headers=request_headers,
)
if r.status_code == 200:
# Breaches found
alias.hibp_breaches = [
Hibp.get_by(name=entry["Name"]) for entry in r.json()
]
if len(alias.hibp_breaches) > 0:
LOG.w("%s appears in HIBP breaches %s", alias, alias.hibp_breaches)
elif r.status_code == 404:
# No breaches found
alias.hibp_breaches = []
else:
LOG.error(
"An error occured while checking alias %s: %s - %s",
alias,
r.status_code,
r.text,
)
return
alias.hibp_last_check = arrow.utcnow()
db.session.add(alias)
db.session.commit()
LOG.d("Updated breaches info for %s", alias)
await asyncio.sleep(1.5)
async def check_hibp():
"""
Check all aliases on the HIBP (Have I Been Pwned) API
"""
LOG.d("Checking HIBP API for aliases in breaches")
if len(HIBP_API_KEYS) == 0:
LOG.exception("No HIBP API keys")
return
LOG.d("Updating list of known breaches")
r = requests.get("https://haveibeenpwned.com/api/v3/breaches")
for entry in r.json():
Hibp.get_or_create(name=entry["Name"])
db.session.commit()
LOG.d("Updated list of known breaches")
LOG.d("Preparing list of aliases to check")
queue = asyncio.Queue()
max_date = arrow.now().shift(days=-HIBP_SCAN_INTERVAL_DAYS)
for alias in (
Alias.query.filter(
or_(Alias.hibp_last_check.is_(None), Alias.hibp_last_check < max_date)
)
.filter(Alias.enabled)
.order_by(Alias.hibp_last_check.asc())
.all()
):
await queue.put(alias.id)
LOG.d("Need to check about %s aliases", queue.qsize())
# Start one checking process per API key
# Each checking process will take one alias from the queue, get the info
# and then sleep for 1.5 seconds (due to HIBP API request limits)
checkers = []
for i in range(len(HIBP_API_KEYS)):
checker = asyncio.create_task(
_hibp_check(
HIBP_API_KEYS[i],
queue,
)
)
checkers.append(checker)
# Wait until all checking processes are done
for checker in checkers:
await checker
LOG.d("Done checking HIBP API for aliases in breaches")
if __name__ == "__main__":
LOG.d("Start running cronjob")
parser = argparse.ArgumentParser()
@ -775,6 +890,7 @@ if __name__ == "__main__":
"sanity_check",
"delete_old_monitoring",
"check_custom_domain",
"check_hibp",
],
)
args = parser.parse_args()
@ -809,3 +925,6 @@ if __name__ == "__main__":
elif args.job == "check_custom_domain":
LOG.d("Check custom domain")
check_custom_domain()
elif args.job == "check_hibp":
LOG.d("Check HIBP")
asyncio.run(check_hibp())

View File

@ -52,3 +52,10 @@ jobs:
shell: /bin/bash
schedule: "0 15 * * *"
captureStderr: true
- name: SimpleLogin HIBP check
command: python /code/cron.py -j check_hibp
shell: /bin/bash
schedule: "0 18 * * *"
captureStderr: true
concurrencyPolicy: Forbid

View File

@ -73,6 +73,7 @@ from app.config import (
TRANSACTIONAL_BOUNCE_PREFIX,
TRANSACTIONAL_BOUNCE_SUFFIX,
ENABLE_SPAM_ASSASSIN,
BOUNCE_PREFIX_FOR_REPLY_PHASE,
)
from app.email.spam import get_spam_score
from app.email_utils import (
@ -595,6 +596,7 @@ def forward_email_to_mailbox(
email_log = EmailLog.create(
contact_id=contact.id, user_id=user.id, mailbox_id=mailbox.id, commit=True
)
LOG.d("Create %s for %s, %s, %s", email_log, contact, user, mailbox)
if ENABLE_SPAM_ASSASSIN:
# Spam check
@ -823,6 +825,7 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
mailbox_id=mailbox.id,
commit=True,
)
LOG.d("Create %s for %s, %s, %s", email_log, contact, user, mailbox)
# Spam check
if ENABLE_SPAM_ASSASSIN:
@ -949,10 +952,12 @@ def handle_reply(envelope, msg: Message, rcpt_to: str) -> (bool, str):
if should_add_dkim_signature(alias_domain):
add_dkim_signature(msg, alias_domain)
# generate a mail_from for VERP
verp_mail_from = f"{BOUNCE_PREFIX_FOR_REPLY_PHASE}+{email_log.id}+@{alias_domain}"
try:
sl_sendmail(
# VERP
BOUNCE_EMAIL.format(email_log.id),
verp_mail_from,
contact.website_email,
msg,
envelope.mail_options,
@ -1570,11 +1575,22 @@ def handle(envelope: Envelope) -> str:
handle_transactional_bounce(envelope, rcpt_tos[0])
return "250 bounce handled"
# whether this is a bounce report
is_bounce = False
if (
len(rcpt_tos) == 1
and rcpt_tos[0].startswith(BOUNCE_PREFIX)
and rcpt_tos[0].endswith(BOUNCE_SUFFIX)
):
is_bounce = True
if len(rcpt_tos) == 1 and rcpt_tos[0].startswith(
f"{BOUNCE_PREFIX_FOR_REPLY_PHASE}+"
):
is_bounce = True
if is_bounce:
return handle_bounce(envelope, rcpt_tos[0], msg)
# Whether it's necessary to apply greylisting
@ -1604,6 +1620,14 @@ def handle(envelope: Envelope) -> str:
# recipient starts with "reply+" or "ra+" (ra=reverse-alias) prefix
if is_reply_email(rcpt_to):
LOG.d("Reply phase %s(%s) -> %s", mail_from, copy_msg["From"], rcpt_to)
# for debugging. A reverse alias should never receive a bounce report from MTA
# as emails are sent with VERP
# but it's possible that some MTA don't send the bounce report correctly
# todo: to remove once this issue is understood
if mail_from == "<>":
LOG.exception("email from <> to reverse alias %s. \n%s", rcpt_to, msg)
is_delivered, smtp_status = handle_reply(envelope, copy_msg, rcpt_to)
res.append((is_delivered, smtp_status))
else: # Forward case

View File

@ -44,6 +44,8 @@ SUPPORT_NAME=Son from SimpleLogin
# prefix must end with + and suffix must start with +
# BOUNCE_PREFIX = "bounces+"
# BOUNCE_SUFFIX = "+@sl.local"
# same as BOUNCE_PREFIX but used for reply phase. Note it doesn't have the plus sign (+) at the end.
# BOUNCE_PREFIX_FOR_REPLY_PHASE = "bounce_reply"
# to receive general stats.
# ADMIN_EMAIL=admin@sl.local
@ -168,4 +170,8 @@ DISABLE_ONBOARDING=true
# ALIAS_LIMIT = "100/day;50/hour;5/minute"
# whether to enable spam scan using SpamAssassin
# ENABLE_SPAM_ASSASSIN = 1
# ENABLE_SPAM_ASSASSIN = 1
# Have I Been Pwned
# HIBP_SCAN_INTERVAL_DAYS = 7
# HIBP_API_KEYS=[]

View File

@ -155,7 +155,7 @@ if __name__ == "__main__":
user = User.get(user_id)
if not user:
LOG.exception("No user found for %s", user_id)
LOG.i("No user found for %s", user_id)
continue
user_email = user.email

View File

@ -0,0 +1,51 @@
"""empty message
Revision ID: 6cc7f073b358
Revises: 5c77d685df87
Create Date: 2021-05-17 21:26:15.007317
"""
import sqlalchemy_utils
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6cc7f073b358'
down_revision = '5c77d685df87'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('hibp',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_hibp_name'), 'hibp', ['name'], unique=True)
op.create_table('alias_hibp',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('created_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=False),
sa.Column('updated_at', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True),
sa.Column('alias_id', sa.Integer(), nullable=True),
sa.Column('hibp_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['alias_id'], ['alias.id'], ),
sa.ForeignKeyConstraint(['hibp_id'], ['hibp.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('alias_id', 'hibp_id', name='uq_alias_hibp')
)
op.add_column('alias', sa.Column('hibp_last_check', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('alias', 'hibp_last_check')
op.drop_table('alias_hibp')
op.drop_index(op.f('ix_hibp_name'), table_name='hibp')
op.drop_table('hibp')
# ### end Alembic commands ###

View File

@ -0,0 +1,35 @@
"""empty message
Revision ID: 68e2f38e33f4
Revises: 6cc7f073b358
Create Date: 2021-05-25 18:13:07.614047
"""
import sqlalchemy_utils
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '68e2f38e33f4'
down_revision = '6cc7f073b358'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('alias_hibp_hibp_id_fkey', 'alias_hibp', type_='foreignkey')
op.drop_constraint('alias_hibp_alias_id_fkey', 'alias_hibp', type_='foreignkey')
op.create_foreign_key(None, 'alias_hibp', 'alias', ['alias_id'], ['id'], ondelete='cascade')
op.create_foreign_key(None, 'alias_hibp', 'hibp', ['hibp_id'], ['id'], ondelete='cascade')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'alias_hibp', type_='foreignkey')
op.drop_constraint(None, 'alias_hibp', type_='foreignkey')
op.create_foreign_key('alias_hibp_alias_id_fkey', 'alias_hibp', 'alias', ['alias_id'], ['id'])
op.create_foreign_key('alias_hibp_hibp_id_fkey', 'alias_hibp', 'hibp', ['hibp_id'], ['id'])
# ### end Alembic commands ###

61
poetry.lock generated
View File

@ -1343,7 +1343,7 @@ python-versions = "*"
[[package]]
name = "requests"
version = "2.24.0"
version = "2.25.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
@ -1351,9 +1351,9 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<4"
chardet = ">=3.0.2,<5"
idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
@ -1740,7 +1740,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "e945d95bf4d631d64f95eb47906d38f20968e4566c75373f3b294df85c6652cc"
content-hash = "7d728cb617b1867a695fdea45c2242b929bffb224fde74ffdb9b4a42083376dd"
[metadata.files]
aiohttp = [
@ -1871,6 +1871,7 @@ cffi = [
{file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"},
{file = "cffi-1.14.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e"},
{file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"},
{file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"},
{file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"},
@ -2189,20 +2190,39 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"},
{file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"},
{file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
mccabe = [
@ -2346,8 +2366,11 @@ psycopg2-binary = [
{file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"},
{file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"},
{file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"},
]
ptyprocess = [
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
@ -2505,6 +2528,8 @@ pyyaml = [
{file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
{file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
{file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
{file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"},
{file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"},
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
regex = [
@ -2528,11 +2553,17 @@ regex = [
{file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"},
{file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"},
{file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"},
{file = "regex-2020.9.27-cp39-cp39-manylinux1_i686.whl", hash = "sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81"},
{file = "regex-2020.9.27-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650"},
{file = "regex-2020.9.27-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67"},
{file = "regex-2020.9.27-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad"},
{file = "regex-2020.9.27-cp39-cp39-win32.whl", hash = "sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302"},
{file = "regex-2020.9.27-cp39-cp39-win_amd64.whl", hash = "sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7"},
{file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"},
]
requests = [
{file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
]
requests-oauthlib = [
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
@ -2555,20 +2586,29 @@ rsa = [
{file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"},
{file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"},
{file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"},
{file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f"},
{file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"},
{file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"},
{file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"},
{file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"},
{file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3"},
{file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"},
{file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"},
{file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"},
{file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"},
{file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4"},
{file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"},
{file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"},
{file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"},
{file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"},
{file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923"},
{file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"},
{file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"},
{file = "ruamel.yaml.clib-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"},
{file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"},
{file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c"},
{file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win32.whl", hash = "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd"},
{file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb"},
{file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"},
]
s3transfer = [
@ -2686,19 +2726,28 @@ typed-ast = [
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"},
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"},
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"},
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"},
{file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"},
{file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
typing-extensions = [

View File

@ -76,6 +76,7 @@ email_validator = "^1.1.1"
PGPy = "^0.5.3"
py3-validate-email = "^0.2.10"
coinbase-commerce = "^1.0.1"
requests = "^2.25.1"
[tool.poetry.dev-dependencies]
pytest = "^6.1.0"

View File

@ -298,6 +298,9 @@ def fake_data():
m1.pgp_finger_print = load_public_key(pgp_public_key)
db.session.commit()
# example@example.com is in a LOT of data breaches
Alias.create(email="example@example.com", user_id=user.id, mailbox_id=m1.id)
for i in range(3):
if i % 2 == 0:
a = Alias.create(

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#DA552F" offset="0%"></stop>
<stop stop-color="#D04B25" offset="100%"></stop>
</linearGradient>
</defs>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g>
<path d="M128,256 C198.6944,256 256,198.6944 256,128 C256,57.3056 198.6944,0 128,0 C57.3056,0 0,57.3056 0,128 C0,198.6944 57.3056,256 128,256 L128,256 Z" fill="url(#linearGradient-1)"></path>
<path d="M96,76.8 L96,179.2 L115.2,179.2 L115.2,147.2 L144.256,147.2 C163.552,146.688 179.2,131.04 179.2,112 C179.2,92.448 163.552,76.8 144.256,76.8 L96,76.8 L96,76.8 Z M144.4928,128 L115.2,128 L115.2,96 L144.4928,96 C153.056,96 160,103.168 160,112 C160,120.832 153.056,128 144.4928,128 L144.4928,128 Z" fill="#FFFFFF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

12
static/logo-white.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.2 KiB

18
static/logo.svg vendored
View File

@ -1,16 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="502.02" height="76.49" viewBox="0 0 502.02 76.49">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="502.02" height="76.496" viewBox="0 0 502.02 76.496">
<defs>
<linearGradient id="linear-gradient" y1="0.5" x2="1" y2="0.5" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#ed2e7c"/>
<stop offset="1" stop-color="#a8288f"/>
</linearGradient>
</defs>
<g id="logo_SimpleLogin_mau_" data-name="logo SimpleLogin mau " transform="translate(-65.99 -190.92)">
<path id="Path_200" data-name="Path 200" d="M440.25,251.83H416.19V205.45h5.43v41.46h18.63Z" fill="#be1e2d"/>
<path id="Path_201" data-name="Path 201" d="M460.5,252.6q-7.335,0-11.72-4.64t-4.38-12.31q0-8.34,4.56-13.03t12.32-4.69q7.41,0,11.56,4.56T477,235.14q0,7.92-4.48,12.69T460.5,252.6Zm.39-30.2a10.1,10.1,0,0,0-8.09,3.48q-2.97,3.48-2.98,9.59,0,5.88,3.01,9.28a10.243,10.243,0,0,0,8.05,3.4c3.43,0,6.06-1.11,7.91-3.33s2.77-5.38,2.77-9.48q0-6.21-2.77-9.57C466.95,223.52,464.31,222.4,460.89,222.4Z" fill="#be1e2d"/>
<path id="Path_202" data-name="Path 202" d="M513.73,249.17q0,18.24-17.46,18.24a23.476,23.476,0,0,1-10.74-2.33v-5.3a22.047,22.047,0,0,0,10.67,3.1q12.225,0,12.22-13v-3.62h-.13a13.394,13.394,0,0,1-21.32,1.93q-3.765-4.41-3.77-11.85,0-8.445,4.06-13.42a13.567,13.567,0,0,1,11.11-4.98q6.69,0,9.93,5.37h.13v-4.59h5.3Zm-5.3-12.32v-4.88a9.481,9.481,0,0,0-2.67-6.76,8.764,8.764,0,0,0-6.65-2.81,9.223,9.223,0,0,0-7.7,3.57q-2.775,3.57-2.78,10.01,0,5.535,2.67,8.85a8.614,8.614,0,0,0,7.07,3.31,9.227,9.227,0,0,0,7.26-3.17A11.838,11.838,0,0,0,508.43,236.85Z" fill="#be1e2d"/>
<path id="Path_203" data-name="Path 203" d="M527.19,210.3a3.358,3.358,0,0,1-2.43-.97,3.538,3.538,0,0,1,0-4.93,3.336,3.336,0,0,1,2.43-.99,3.411,3.411,0,0,1,2.47.99,3.452,3.452,0,0,1,0,4.9A3.38,3.38,0,0,1,527.19,210.3Zm2.58,41.53h-5.3V218.71h5.3Z" fill="#be1e2d"/>
<path id="Path_204" data-name="Path 204" d="M568,251.83h-5.3V232.94q0-10.545-7.7-10.54a8.353,8.353,0,0,0-6.58,2.99,11.053,11.053,0,0,0-2.6,7.55v18.89h-5.3V218.71h5.3v5.5h.13a11.951,11.951,0,0,1,10.87-6.27q5.43,0,8.31,3.51t2.88,10.14v20.24Z" fill="#be1e2d"/>
<path id="Path_205" data-name="Path 205" d="M216.13,249.95v-6.4a12.23,12.23,0,0,0,2.64,1.75,21.392,21.392,0,0,0,3.23,1.31,24.491,24.491,0,0,0,3.41.82,19.438,19.438,0,0,0,3.17.29q5.01,0,7.49-1.86a6.994,6.994,0,0,0,1.65-8.62,9.326,9.326,0,0,0-2.28-2.54,23.234,23.234,0,0,0-3.44-2.2c-1.33-.7-2.75-1.44-4.29-2.22q-2.43-1.23-4.53-2.49a19.572,19.572,0,0,1-3.65-2.78,11.607,11.607,0,0,1-2.44-3.44,10.7,10.7,0,0,1-.89-4.51,10.565,10.565,0,0,1,1.39-5.51,12.052,12.052,0,0,1,3.65-3.87,16.516,16.516,0,0,1,5.16-2.26,23.441,23.441,0,0,1,5.9-.74q6.855,0,9.99,1.65v6.11a18.09,18.09,0,0,0-10.54-2.85,17.219,17.219,0,0,0-3.56.37,10.1,10.1,0,0,0-3.17,1.21,6.9,6.9,0,0,0-2.26,2.17,5.749,5.749,0,0,0-.87,3.23,6.654,6.654,0,0,0,.66,3.07,7.422,7.422,0,0,0,1.96,2.36,19.539,19.539,0,0,0,3.15,2.07q1.86,1,4.29,2.2,2.49,1.23,4.72,2.59a21.626,21.626,0,0,1,3.91,3.01,13.537,13.537,0,0,1,2.67,3.65,10.255,10.255,0,0,1,.99,4.59,11.651,11.651,0,0,1-1.34,5.8,11.092,11.092,0,0,1-3.62,3.87,15.783,15.783,0,0,1-5.26,2.15,28.839,28.839,0,0,1-6.27.66,25.406,25.406,0,0,1-2.72-.18c-1.08-.12-2.18-.29-3.3-.52a29.1,29.1,0,0,1-3.19-.84A9.4,9.4,0,0,1,216.13,249.95Zm39.36-39.65a3.358,3.358,0,0,1-2.43-.97,3.538,3.538,0,0,1,0-4.93,3.336,3.336,0,0,1,2.43-.99,3.411,3.411,0,0,1,2.47.99,3.452,3.452,0,0,1,0,4.9A3.38,3.38,0,0,1,255.49,210.3Zm2.59,41.53h-5.3V218.71h5.3Zm57.76,0h-5.3V232.81q0-5.5-1.7-7.96t-5.71-2.46a7.057,7.057,0,0,0-5.77,3.1,11.911,11.911,0,0,0-2.38,7.44v18.89h-5.3V232.16q0-9.765-7.54-9.77a6.992,6.992,0,0,0-5.76,2.93,12.094,12.094,0,0,0-2.26,7.62v18.89h-5.3V218.71h5.3v5.24h.13a11.256,11.256,0,0,1,10.28-6.02,9.563,9.563,0,0,1,9.38,6.85q3.69-6.855,11-6.86,10.935,0,10.93,13.49Zm15.46-4.79h-.13v20.02h-5.3V218.71h5.3v5.82h.13a12.537,12.537,0,0,1,11.45-6.6q6.4,0,9.99,4.45t3.59,11.92q0,8.31-4.04,13.31a13.469,13.469,0,0,1-11.06,5A11.085,11.085,0,0,1,331.3,247.04Zm-.13-13.36v4.62a9.844,9.844,0,0,0,2.67,6.97,9.521,9.521,0,0,0,14.33-.83q2.73-3.69,2.73-10.25,0-5.535-2.55-8.67a8.456,8.456,0,0,0-6.92-3.14,9.4,9.4,0,0,0-7.44,3.22A11.812,11.812,0,0,0,331.17,233.68Zm38.93,18.15h-5.3V202.8h5.3Zm37.36-15.24H384.08c.09,3.69,1.08,6.53,2.98,8.54a10.253,10.253,0,0,0,7.83,3.01,16.239,16.239,0,0,0,10.28-3.69v4.98q-4.365,3.165-11.55,3.17-7.02,0-11.03-4.51t-4.01-12.69q0-7.725,4.38-12.6a14.067,14.067,0,0,1,10.88-4.87q6.495,0,10.06,4.2t3.56,11.67Zm-5.43-4.49a10.834,10.834,0,0,0-2.21-7.15,7.563,7.563,0,0,0-6.06-2.55,8.6,8.6,0,0,0-6.37,2.68,12.162,12.162,0,0,0-3.23,7.02ZM98.7,190.92l-.02.02h.04Zm93.48.02H98.68l-1.18,1.22-.2.22c-.9.94-1.84,1.84-2.78,2.69a59.888,59.888,0,0,1-25.13,14.02c-.27.08-.53.14-.8.22l-1.18.29-.27,1.18c-.1.43-.18.86-.27,1.31a49.81,49.81,0,0,0-.88,9.21A48.915,48.915,0,0,0,95.2,266.07c.2.1.43.2.65.29.49.22.98.41,1.47.61l.59.22.08.02.04-.02a.341.341,0,0,0,.14.02h93.99a3.666,3.666,0,0,0,3.67-3.67V194.58A3.617,3.617,0,0,0,192.18,190.94Zm-6.35,5.86L146.7,230.91a2.129,2.129,0,0,1-2.8-.02l-12.76-11.31a46.482,46.482,0,0,0-.88-8.1q-.06-.33-.12-.69l-.27-1.18-1.18-.31c-.29-.06-.57-.14-.86-.2a58.425,58.425,0,0,1-18.17-8.55l-4.23-3.74h80.4Zm-52.67,32.19-3.21,2.84a49.547,49.547,0,0,0,.86-4.92ZM98.07,263.06c-1.2-.49-2.39-1.04-3.55-1.63a45.314,45.314,0,0,1-24.6-40.11,43.712,43.712,0,0,1,.82-8.49,63.514,63.514,0,0,0,23.78-12.21c1.45-1.16,2.84-2.41,4.18-3.72a63.629,63.629,0,0,0,23.09,14.39c1.55.57,3.12,1.06,4.74,1.49.18,1.02.35,2.06.45,3.1a46.71,46.71,0,0,1,.29,5.04c0,.92-.02,1.84-.1,2.76a44.156,44.156,0,0,1-2.8,13.09A45.171,45.171,0,0,1,98.07,263.06Zm11.41-1.51a49,49,0,0,0,16.27-18.29l11.74-10.37,6.37,5.7a2.133,2.133,0,0,0,2.82,0l6.49-5.76,32.58,28.68Zm48.12-32.58L190.2,200l.06,57.77Zm-50.73,23.21a32.162,32.162,0,0,0,4.33-3.82ZM121.4,218.6c-.1-.82-.22-1.63-.37-2.45-1.29-.35-2.55-.76-3.8-1.22a.02.02,0,0,1-.02-.02c-.33-.1-.63-.24-.96-.37-.71-.29-1.43-.59-2.14-.94-.49-.22-1-.45-1.49-.71-.35-.16-.71-.35-1.06-.55-1.12-.59-2.23-1.25-3.31-1.94-.67-.41-1.33-.86-1.96-1.31a.359.359,0,0,1-.14-.1c-.69-.47-1.37-.98-2.04-1.49-1.33-1.02-2.61-2.1-3.86-3.25-.53-.47-1.06-.96-1.57-1.47q-2,1.935-4.16,3.72a50.115,50.115,0,0,1-18.31,9.7,36.36,36.36,0,0,0-.65,6.84,37,37,0,0,0,18.97,32.21,35.942,35.942,0,0,0,3.65,1.74c.73-.29,1.47-.59,2.18-.92a.07.07,0,0,0,.04-.02c.59-.27,1.18-.55,1.76-.86a17.078,17.078,0,0,0,1.55-.88,4.188,4.188,0,0,0,.49-.29c.27-.16.53-.33.78-.49a.481.481,0,0,0,.16-.12,15.673,15.673,0,0,0,1.33-.92,1.7,1.7,0,0,0,.41-.31l4.33-3.82c.04,0,.04,0,.04-.02a37.109,37.109,0,0,0,9.08-16,36.106,36.106,0,0,0,1.31-9.64A40.26,40.26,0,0,0,121.4,218.6Zm-21.08,20.98-3.33,3.33h-.02L83.19,229.13l4.23-4.27,9.57,9.57,3.33-3.33,11.62-11.64,4.25,4.25Z" fill="url(#linear-gradient)"/>
<g id="Group_94" data-name="Group 94" transform="translate(628 -119)">
<g id="Group_86" data-name="Group 86">
<path id="Path_200" data-name="Path 200" d="M440.25,251.83H416.19V205.45h5.43v41.46h18.63Z" transform="translate(-693.99 -71.92)" fill="#be1e2d"/>
<path id="Path_201" data-name="Path 201" d="M460.5,252.6q-7.335,0-11.72-4.64t-4.38-12.31q0-8.34,4.56-13.03t12.32-4.69q7.41,0,11.56,4.56T477,235.14q0,7.92-4.48,12.69T460.5,252.6Zm.39-30.2a10.1,10.1,0,0,0-8.09,3.48q-2.97,3.48-2.98,9.59,0,5.88,3.01,9.28a10.243,10.243,0,0,0,8.05,3.4c3.43,0,6.06-1.11,7.91-3.33s2.77-5.38,2.77-9.48q0-6.21-2.77-9.57c-1.84-2.25-4.48-3.37-7.9-3.37Z" transform="translate(-693.99 -71.92)" fill="#be1e2d"/>
<path id="Path_202" data-name="Path 202" d="M513.73,249.17q0,18.24-17.46,18.24a23.476,23.476,0,0,1-10.74-2.33v-5.3a22.047,22.047,0,0,0,10.67,3.1q12.225,0,12.22-13v-3.62h-.13a13.394,13.394,0,0,1-21.32,1.93q-3.765-4.41-3.77-11.85,0-8.445,4.06-13.42a13.567,13.567,0,0,1,11.11-4.98q6.69,0,9.93,5.37h.13v-4.59h5.3Zm-5.3-12.32v-4.88a9.481,9.481,0,0,0-2.67-6.76,8.764,8.764,0,0,0-6.65-2.81,9.223,9.223,0,0,0-7.7,3.57q-2.775,3.57-2.78,10.01,0,5.535,2.67,8.85a8.614,8.614,0,0,0,7.07,3.31,9.227,9.227,0,0,0,7.26-3.17,11.838,11.838,0,0,0,2.8-8.12Z" transform="translate(-693.99 -71.92)" fill="#be1e2d"/>
<path id="Path_203" data-name="Path 203" d="M527.19,210.3a3.358,3.358,0,0,1-2.43-.97,3.538,3.538,0,0,1,0-4.93,3.336,3.336,0,0,1,2.43-.99,3.411,3.411,0,0,1,2.47.99,3.452,3.452,0,0,1,0,4.9,3.38,3.38,0,0,1-2.47,1Zm2.58,41.53h-5.3V218.71h5.3Z" transform="translate(-693.99 -71.92)" fill="#be1e2d"/>
<path id="Path_204" data-name="Path 204" d="M568,251.83h-5.3V232.94q0-10.545-7.7-10.54a8.353,8.353,0,0,0-6.58,2.99,11.053,11.053,0,0,0-2.6,7.55v18.89h-5.3V218.71h5.3v5.5h.13a11.951,11.951,0,0,1,10.87-6.27q5.43,0,8.31,3.51t2.88,10.14v20.24Z" transform="translate(-693.99 -71.92)" fill="#be1e2d"/>
</g>
<path id="Path_205" data-name="Path 205" d="M216.13,249.95v-6.4a12.231,12.231,0,0,0,2.64,1.75,21.389,21.389,0,0,0,3.23,1.31,24.494,24.494,0,0,0,3.41.82,19.437,19.437,0,0,0,3.17.29q5.01,0,7.49-1.86a6.994,6.994,0,0,0,1.65-8.62,9.326,9.326,0,0,0-2.28-2.54,23.234,23.234,0,0,0-3.44-2.2c-1.33-.7-2.75-1.44-4.29-2.22q-2.43-1.23-4.53-2.49a19.572,19.572,0,0,1-3.65-2.78,11.607,11.607,0,0,1-2.44-3.44,10.7,10.7,0,0,1-.89-4.51,10.565,10.565,0,0,1,1.39-5.51,12.052,12.052,0,0,1,3.65-3.87,16.516,16.516,0,0,1,5.16-2.26,23.441,23.441,0,0,1,5.9-.74q6.855,0,9.99,1.65v6.11a18.09,18.09,0,0,0-10.54-2.85,17.219,17.219,0,0,0-3.56.37,10.1,10.1,0,0,0-3.17,1.21,6.9,6.9,0,0,0-2.26,2.17,5.749,5.749,0,0,0-.87,3.23,6.654,6.654,0,0,0,.66,3.07,7.422,7.422,0,0,0,1.96,2.36,19.538,19.538,0,0,0,3.15,2.07q1.86,1,4.29,2.2,2.49,1.23,4.72,2.59a21.626,21.626,0,0,1,3.91,3.01,13.537,13.537,0,0,1,2.67,3.65,10.255,10.255,0,0,1,.99,4.59,11.651,11.651,0,0,1-1.34,5.8,11.092,11.092,0,0,1-3.62,3.87,15.783,15.783,0,0,1-5.26,2.15,28.836,28.836,0,0,1-6.27.66,25.414,25.414,0,0,1-2.72-.18c-1.08-.12-2.18-.29-3.3-.52a29.1,29.1,0,0,1-3.19-.84,9.4,9.4,0,0,1-2.41-1.1Zm39.36-39.65a3.358,3.358,0,0,1-2.43-.97,3.538,3.538,0,0,1,0-4.93,3.336,3.336,0,0,1,2.43-.99,3.411,3.411,0,0,1,2.47.99,3.452,3.452,0,0,1,0,4.9,3.38,3.38,0,0,1-2.47,1Zm2.59,41.53h-5.3V218.71h5.3Zm57.76,0h-5.3V232.81q0-5.5-1.7-7.96t-5.71-2.46a7.057,7.057,0,0,0-5.77,3.1,11.911,11.911,0,0,0-2.38,7.44v18.89h-5.3V232.16q0-9.765-7.54-9.77a6.992,6.992,0,0,0-5.76,2.93,12.094,12.094,0,0,0-2.26,7.62v18.89h-5.3V218.71h5.3v5.24h.13a11.256,11.256,0,0,1,10.28-6.02,9.563,9.563,0,0,1,9.38,6.85q3.69-6.855,11-6.86,10.935,0,10.93,13.49Zm15.46-4.79h-.13v20.02h-5.3V218.71h5.3v5.82h.13a12.537,12.537,0,0,1,11.45-6.6,12.121,12.121,0,0,1,9.99,4.45q3.59,4.45,3.59,11.92,0,8.31-4.04,13.31a13.469,13.469,0,0,1-11.06,5,11.085,11.085,0,0,1-9.93-5.57Zm-.13-13.36v4.62a9.844,9.844,0,0,0,2.67,6.97,9.521,9.521,0,0,0,14.33-.83q2.73-3.69,2.73-10.25,0-5.535-2.55-8.67a8.456,8.456,0,0,0-6.92-3.14,9.4,9.4,0,0,0-7.44,3.22A11.812,11.812,0,0,0,331.17,233.68Zm38.93,18.15h-5.3V202.8h5.3Zm37.36-15.24H384.08c.09,3.69,1.08,6.53,2.98,8.54a10.253,10.253,0,0,0,7.83,3.01,16.239,16.239,0,0,0,10.28-3.69v4.98q-4.365,3.165-11.55,3.17-7.02,0-11.03-4.51t-4.01-12.69q0-7.725,4.38-12.6a14.067,14.067,0,0,1,10.88-4.87q6.5,0,10.06,4.2t3.56,11.67Zm-5.43-4.49a10.834,10.834,0,0,0-2.21-7.15,7.563,7.563,0,0,0-6.06-2.55,8.6,8.6,0,0,0-6.37,2.68,12.162,12.162,0,0,0-3.23,7.02ZM98.7,190.92l-.02.02h.04Zm93.48.02H98.68l-1.18,1.22-.2.22c-.9.94-1.84,1.84-2.78,2.69a59.888,59.888,0,0,1-25.13,14.02c-.27.08-.53.14-.8.22l-1.18.29-.27,1.18c-.1.43-.18.86-.27,1.31a49.81,49.81,0,0,0-.88,9.21A48.915,48.915,0,0,0,95.2,266.07c.2.1.43.2.65.29.49.22.98.41,1.47.61l.59.22.08.02.04-.02a.341.341,0,0,0,.14.02h93.99a3.666,3.666,0,0,0,3.67-3.67V194.58a3.617,3.617,0,0,0-3.65-3.64Zm-6.35,5.86L146.7,230.91a2.129,2.129,0,0,1-2.8-.02l-12.76-11.31a46.482,46.482,0,0,0-.88-8.1q-.06-.33-.12-.69l-.27-1.18-1.18-.31c-.29-.06-.57-.14-.86-.2a58.425,58.425,0,0,1-18.17-8.55l-4.23-3.74h80.4Zm-52.67,32.19-3.21,2.84a49.554,49.554,0,0,0,.86-4.92ZM98.07,263.06c-1.2-.49-2.39-1.04-3.55-1.63a45.314,45.314,0,0,1-24.6-40.11,43.712,43.712,0,0,1,.82-8.49,63.514,63.514,0,0,0,23.78-12.21c1.45-1.16,2.84-2.41,4.18-3.72a63.629,63.629,0,0,0,23.09,14.39c1.55.57,3.12,1.06,4.74,1.49.18,1.02.35,2.06.45,3.1a46.713,46.713,0,0,1,.29,5.04c0,.92-.02,1.84-.1,2.76a44.156,44.156,0,0,1-2.8,13.09A45.171,45.171,0,0,1,98.07,263.06Zm11.41-1.51a49,49,0,0,0,16.27-18.29l11.74-10.37,6.37,5.7a2.133,2.133,0,0,0,2.82,0l6.49-5.76,32.58,28.68Zm48.12-32.58L190.2,200l.06,57.77Zm-50.73,23.21a32.161,32.161,0,0,0,4.33-3.82ZM121.4,218.6c-.1-.82-.22-1.63-.37-2.45-1.29-.35-2.55-.76-3.8-1.22a.02.02,0,0,1-.02-.02c-.33-.1-.63-.24-.96-.37-.71-.29-1.43-.59-2.14-.94-.49-.22-1-.45-1.49-.71-.35-.16-.71-.35-1.06-.55-1.12-.59-2.23-1.25-3.31-1.94-.67-.41-1.33-.86-1.96-1.31a.359.359,0,0,1-.14-.1c-.69-.47-1.37-.98-2.04-1.49-1.33-1.02-2.61-2.1-3.86-3.25-.53-.47-1.06-.96-1.57-1.47q-2,1.935-4.16,3.72a50.115,50.115,0,0,1-18.31,9.7,36.36,36.36,0,0,0-.65,6.84,37,37,0,0,0,18.97,32.21,35.939,35.939,0,0,0,3.65,1.74c.73-.29,1.47-.59,2.18-.92a.07.07,0,0,0,.04-.02c.59-.27,1.18-.55,1.76-.86a17.076,17.076,0,0,0,1.55-.88,4.185,4.185,0,0,0,.49-.29c.27-.16.53-.33.78-.49a.481.481,0,0,0,.16-.12,15.669,15.669,0,0,0,1.33-.92,1.7,1.7,0,0,0,.41-.31l4.33-3.82c.04,0,.04,0,.04-.02a37.109,37.109,0,0,0,9.08-16,36.106,36.106,0,0,0,1.31-9.64,40.262,40.262,0,0,0-.24-4.1Zm-21.08,20.98-3.33,3.33h-.02L83.19,229.13l4.23-4.27,9.57,9.57,3.33-3.33,11.62-11.64,4.25,4.25Z" transform="translate(-693.99 -71.92)" fill="url(#linear-gradient)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

10
static/style.css vendored
View File

@ -103,4 +103,14 @@ em {
color: red;
font-style: italic;
margin-bottom: 1em;
}
.footer-item {
padding-left: 0 !important;
font-size: 14px;
}
.footer-list-group {
list-style: none;
padding-left: 0;
}

View File

@ -102,13 +102,16 @@
<body class="">
<div class="page">
{% block announcement %}
<div class="alert alert-warning text-center mb-0" role="alert"
style="color: white; background-color: #ea532a; font-weight: 500;">
<a href="https://www.producthunt.com/posts/simplelogin" target="_blank" style="color: white" >
Support SimpleLogin on ProductHunt
<i class="fe fe-external-link"></i>
</a>
</div>
<div class="text-center mb-0" role="alert" style="color: white; background-color: #e96a48; font-weight: 500;">
SimpleLogin is on ProductHunt
<img src="/static/images/producthunt.svg" style="width: 18px">
,
<a href="https://www.producthunt.com/posts/simplelogin" target="_blank"
style="font-weight: 500; color: white; text-decoration: underline;">
Support Us
<i class="fe fe-external-link"></i>
</a>
</div>
{% endblock %}
<div class="container">

View File

@ -5,7 +5,7 @@
{% include "header.html" %}
<div class="my-2 my-md-2">
<div class="container pt-1">
<div class="container pt-1" style="min-height: 800px">
{% block default_content %}
{% endblock %}
</div>

View File

@ -1,62 +1,166 @@
<footer class="footer">
<div class="container">
<div class="row align-items-center flex-row-reverse">
<div class="col-auto ml-lg-auto">
<div class="row align-items-center">
<div class="col-auto">
<ul class="list-inline list-inline-dots mb-0">
<li class="list-inline-item"><a href="https://github.com/simple-login/app/discussions" target="_blank" rel="noopener">
Forum <i class="fe fe-external-link"></i>
</a></li>
<!-- ========== FOOTER ========== -->
<footer class="gradient-half-primary-v1" id="footer"
style="background-image: linear-gradient( 150deg ,#2d1582,#19a0ff); background-repeat: repeat-x; list-style: none initial; ">
<div class="container space-top-2 space-bottom-1 mt-7">
<div class="row justify-content-lg-start mb-7">
<div class="col-sm-7 col-lg-6">
<!-- Logo -->
<a
href='https://simplelogin.io/'
aria-label="SimpleLogin">
<img src="/static/logo-white.svg" height="30px" class="mb-3" alt="SimpleLogin logo">
</a>
<!-- End Logo -->
<li class="list-inline-item">
<a href="https://addons.mozilla.org/firefox/addon/simplelogin/" target="_blank" rel="noopener">
Firefox Add-on<i class="fe fe-external-link"></i>
</a>
</li>
<li class="list-inline-item">
<a href="https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn" target="_blank" rel="noopener">
Chrome Extension<i class="fe fe-external-link"></i>
</a>
</li>
<p class="small text-white">
SimpleLogin is an open-source email alias solution to protect your email address.
</p>
<li class="list-inline-item">
<a href="https://apps.apple.com/app/id1494359858" target="_blank" rel="noopener">
App Store<i class="fe fe-external-link"></i>
</a>
</li>
<p class="small text-white">
SimpleLogin is the product of SimpleLogin SAS, registered in France under the SIREN number 884302134.
</p>
<li class="list-inline-item">
<a href="https://play.google.com/store/apps/details?id=io.simplelogin.android" target="_blank" rel="noopener">
Play Store<i class="fe fe-external-link"></i>
</a>
</li>
<li class="list-inline-item"><a href="{{STATUS_PAGE_URL}}" target="_blank" rel="noopener">
Status <i class="fe fe-external-link"></i>
</a></li>
<li class="list-inline-item"><a href="mailto:{{SUPPORT_EMAIL}}">Contact Us
<i class="fe fe-external-link"></i></a></li>
<li class="list-inline-item intro-step-0">
<a onclick="startIntro()"
data-intro="Welcome to SimpleLogin! <br><br>
It seems that this is the first time you are here,
let's walk through some SimpleLogin features together! <br><br>
You can always show this tutorial again any time by clicking on this <i class='fe fe-help-circle'></i> icon below 👇"
data-step="1"
><i class="fe fe-help-circle"></i></a></li>
</ul>
</div>
</div>
</div>
<div class="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
© {{ YEAR }}
<a href="https://simplelogin.io" target="_blank" rel="noopener">SimpleLogin</a>.
</div>
<div class="row">
<div class="col-sm-4 col-lg-3 mb-4">
<h3 class="h4 text-white">Developer</h3>
<ul class="list-group list-group-transparent list-group-white list-group-flush list-group-borderless mb-0 footer-list-group">
<li>
<a class="list-group-item text-white footer-item " href="https://simplelogin.io/developer">
Sign in with SimpleLogin
</a>
</li>
<li><a class="list-group-item text-white footer-item " href="https://docs.simplelogin.io">Documentation</a>
</li>
<li><a class="list-group-item text-white footer-item " href="https://github.com/simple-login/app">Github
<img src="https://img.shields.io/github/stars/simple-login/app?style=social" alt="GitHub">
</a></li>
<li><a class="list-group-item text-white footer-item " href="https://simple-login.github.io/uptime/">Status
</a></li>
</ul>
</div>
<div class="col-sm-4 col-lg-2 mb-4">
<h3 class="h4 text-white">Company</h3>
<ul class="list-group list-group-transparent list-group-white list-group-flush list-group-borderless mb-0 footer-list-group">
<li>
<a class="list-group-item text-white footer-item"
href='https://simplelogin.io/pricing'>
Pricing
</a>
</li>
<li><a class="list-group-item text-white footer-item" href="https://simplelogin.io/blog">Blog</a></li>
<li><a class="list-group-item text-white footer-item" href="https://simplelogin.io/job">Join Us</a></li>
<li><a class="list-group-item text-white footer-item" href="https://simplelogin.io/about">About Us</a></li>
<li><a class="list-group-item text-white footer-item"
href="https://github.com/simple-login/app/projects/1">Roadmap</a></li>
<li><a class="list-group-item text-white footer-item"
href="https://simplelogin.io/contact">Contact Us</a></li>
<li><a class="list-group-item text-white footer-item"
href="https://simplelogin.io/imprint">Imprint</a></li>
</ul>
</div>
<div class="col-sm-4 col-lg-2 mb-4">
<h3 class="h4 text-white">Resources</h3>
<ul class="list-group list-group-transparent list-group-white list-group-flush list-group-borderless mb-0 footer-list-group">
<li><a class="list-group-item text-white footer-item " href="https://simplelogin.io/terms">Terms</a></li>
<li><a class="list-group-item text-white footer-item " href="https://simplelogin.io/privacy">Privacy</a></li>
<li><a class="list-group-item text-white footer-item " href="https://simplelogin.io/security">Security</a>
</li>
<li>
<a class="list-group-item text-white footer-item"
href='https://simplelogin.io/faq'>
FAQ
</a>
</li>
</ul>
</div>
<div class="col-sm-4 col-lg-2 mb-4">
<h3 class="h4 text-white">Comparisons</h3>
<ul class="list-group list-group-transparent list-group-white list-group-flush list-group-borderless mb-0 footer-list-group">
<li><a class="list-group-item text-white footer-item" href="https://simplelogin.io/blog/vs-firefox-relay/">vs
Firefox Relay</a></li>
<li><a class="list-group-item text-white footer-item"
href="https://simplelogin.io/blog/email-alias-vs-plus-sign/">vs Plus Sign (+) Trick </a></li>
</ul>
</div>
<div class="col-sm-4 col-lg-2 mb-4">
<h3 class="h6 text-white">Features</h3>
<ul class="list-group list-group-transparent list-group-white list-group-flush list-group-borderless mb-0 footer-list-group">
<li><a class="list-group-item text-white footer-item " rel="noopener"
href="https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn">Chrome Extension</a>
</li>
<li><a class="list-group-item text-white footer-item " rel="noopener"
href="https://addons.mozilla.org/firefox/addon/simplelogin/">Firefox Add-on</a></li>
<li><a class="list-group-item text-white footer-item " rel="noopener"
href="https://apps.apple.com/app/id1494051017">Safari
Extension</a></li>
<li><a class="list-group-item text-white footer-item " rel="noopener"
href="https://apps.apple.com/app/id1494359858">iOS
(App Store)</a></li>
<li><a class="list-group-item text-white footer-item " rel="noopener"
href="https://play.google.com/store/apps/details?id=io.simplelogin.android">Android (Play Store)</a>
</li>
<li><a class="list-group-item text-white footer-item " rel="noopener"
href="https://f-droid.org/en/packages/io.simplelogin.android.fdroid/">Android (F-Droid)</a></li>
</ul>
</div>
</div>
<div class="row align-items-center">
<div class="col-sm-6 mb-3 mb-sm-0">
<!-- Social Networks -->
<ul class="list-inline mb-0">
<li class="list-inline-item">
<a class="btn btn-sm btn-icon btn-soft-light btn-bg-transparent" href="https://twitter.com/simple_login">
<span class="fa fa-twitter btn-icon__inner text-white"></span>
</a>
</li>
<li class="list-inline-item">
<a class="btn btn-sm btn-icon btn-soft-light btn-bg-transparent"
href="https://www.reddit.com/r/Simplelogin/">
<span class="fa fa-reddit btn-icon__inner text-white"></span>
</a>
</li>
<li class="list-inline-item">
<a class="btn btn-sm btn-icon btn-soft-light btn-bg-transparent"
href="https://www.producthunt.com/posts/simplelogin">
<span class="fa fa-product-hunt btn-icon__inner text-white"></span>
</a>
</li>
</ul>
<!-- End Social Networks -->
</div>
</div>
</div>
</footer>
<!-- ========== END FOOTER ========== -->
<script>
function startIntro() {
@ -137,4 +241,4 @@
}
}
})
</script>
</script>

View File

@ -9,12 +9,24 @@
<div class="nav-item" data-toggle="dark-mode" title='Toggle bright/dark mode'>
<i class="fe fe-moon"></i>
</div>
{% if current_user.should_show_upgrade_button() %}
<div class="nav-item">
<a href="{{ url_for('dashboard.pricing') }}" class="btn btn-sm btn-outline-primary">Upgrade</a>
</div>
{% endif %}
<div class="nav-item">
<a onclick="startIntro()"
data-intro="Welcome to SimpleLogin! <br><br>
It seems that this is the first time you are here,
let's walk through some SimpleLogin features together! <br><br>
You can always show this tutorial again any time by clicking on this <i class='fe fe-help-circle'></i> icon above 👆"
data-step="1"
><i class="fe fe-help-circle"></i></a>
</div>
<div id="notification-app" class="dropdown d-none d-md-flex" v-if="showNotification">
<a class="nav-link icon" data-toggle="collapse" href="#notifications" style="height: 100%">

View File

@ -128,6 +128,12 @@ def test_parseaddr_unicode():
"abcd@gmail.com",
)
# when a name can't be decoded, return an empty string
assert parseaddr_unicode("=?UTF-8?B?Cec<65><63><EFBFBD>?= <test@example.com>") == (
"",
"test@example.com",
)
def test_send_email_with_rate_control(flask_client):
user = User.create(
@ -712,4 +718,5 @@ def test_should_disable_bounce_consecutive_days(flask_client):
def test_parse_id_from_bounce():
assert parse_id_from_bounce("bounces+1234+@local") == 1234
assert parse_id_from_bounce("anything+1234+@local") == 1234
assert parse_id_from_bounce(BOUNCE_EMAIL.format(1234)) == 1234