Add code verification for creating mailboxes (#1725)
* Add code verification for creating mailboxes * Added validation checks * Use exceptions * Added delete to the mailbox utils * Fix test * Update package.lock * Fix delete error --------- Co-authored-by: Adrià Casajús <adria.casajus@proton.ch>
This commit is contained in:
parent
5ddbca05b2
commit
a5e7da10dd
|
@ -7,15 +7,14 @@ from flask import request
|
||||||
|
|
||||||
from app.api.base import api_bp, require_api_auth
|
from app.api.base import api_bp, require_api_auth
|
||||||
from app.config import JOB_DELETE_MAILBOX
|
from app.config import JOB_DELETE_MAILBOX
|
||||||
from app.dashboard.views.mailbox import send_verification_email
|
|
||||||
from app.dashboard.views.mailbox_detail import verify_mailbox_change
|
from app.dashboard.views.mailbox_detail import verify_mailbox_change
|
||||||
from app.db import Session
|
from app.db import Session
|
||||||
from app.email_utils import (
|
from app.email_utils import (
|
||||||
mailbox_already_used,
|
mailbox_already_used,
|
||||||
email_can_be_used_as_mailbox,
|
email_can_be_used_as_mailbox,
|
||||||
is_valid_email,
|
|
||||||
)
|
)
|
||||||
from app.log import LOG
|
from app.log import LOG
|
||||||
|
from app.mailbox_utils import create_mailbox_and_send_verification, MailboxError
|
||||||
from app.models import Mailbox, Job
|
from app.models import Mailbox, Job
|
||||||
from app.utils import sanitize_email
|
from app.utils import sanitize_email
|
||||||
|
|
||||||
|
@ -46,29 +45,14 @@ def create_mailbox():
|
||||||
|
|
||||||
if not user.is_premium():
|
if not user.is_premium():
|
||||||
return jsonify(error=f"Only premium plan can add additional mailbox"), 400
|
return jsonify(error=f"Only premium plan can add additional mailbox"), 400
|
||||||
|
try:
|
||||||
if not is_valid_email(mailbox_email):
|
mailbox = create_mailbox_and_send_verification(user, mailbox_email)
|
||||||
return jsonify(error=f"{mailbox_email} invalid"), 400
|
|
||||||
elif mailbox_already_used(mailbox_email, user):
|
|
||||||
return jsonify(error=f"{mailbox_email} already used"), 400
|
|
||||||
elif not email_can_be_used_as_mailbox(mailbox_email):
|
|
||||||
return (
|
return (
|
||||||
jsonify(
|
jsonify(mailbox_to_dict(mailbox)),
|
||||||
error=f"{mailbox_email} cannot be used. Please note a mailbox cannot "
|
|
||||||
f"be a disposable email address"
|
|
||||||
),
|
|
||||||
400,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
new_mailbox = Mailbox.create(email=mailbox_email, user_id=user.id)
|
|
||||||
Session.commit()
|
|
||||||
|
|
||||||
send_verification_email(user, new_mailbox)
|
|
||||||
|
|
||||||
return (
|
|
||||||
jsonify(mailbox_to_dict(new_mailbox)),
|
|
||||||
201,
|
201,
|
||||||
)
|
)
|
||||||
|
except MailboxError as e:
|
||||||
|
return jsonify(error=str(e)), 400
|
||||||
|
|
||||||
|
|
||||||
@api_bp.route("/mailboxes/<int:mailbox_id>", methods=["DELETE"])
|
@api_bp.route("/mailboxes/<int:mailbox_id>", methods=["DELETE"])
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import arrow
|
|
||||||
from flask import render_template, request, redirect, url_for, flash
|
from flask import render_template, request, redirect, url_for, flash
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
@ -7,18 +6,17 @@ from wtforms import validators, IntegerField
|
||||||
from wtforms.fields.html5 import EmailField
|
from wtforms.fields.html5 import EmailField
|
||||||
|
|
||||||
from app import parallel_limiter
|
from app import parallel_limiter
|
||||||
from app.config import MAILBOX_SECRET, URL, JOB_DELETE_MAILBOX
|
from app.config import MAILBOX_SECRET
|
||||||
from app.dashboard.base import dashboard_bp
|
from app.dashboard.base import dashboard_bp
|
||||||
from app.db import Session
|
from app.db import Session
|
||||||
from app.email_utils import (
|
|
||||||
email_can_be_used_as_mailbox,
|
|
||||||
mailbox_already_used,
|
|
||||||
render,
|
|
||||||
send_email,
|
|
||||||
is_valid_email,
|
|
||||||
)
|
|
||||||
from app.log import LOG
|
from app.log import LOG
|
||||||
from app.models import Mailbox, Job
|
from app.mailbox_utils import (
|
||||||
|
create_mailbox_and_send_verification,
|
||||||
|
set_mailbox_verified,
|
||||||
|
MailboxError,
|
||||||
|
delete_mailbox,
|
||||||
|
)
|
||||||
|
from app.models import Mailbox
|
||||||
from app.utils import CSRFValidationForm
|
from app.utils import CSRFValidationForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,53 +52,16 @@ def mailbox_route():
|
||||||
if not delete_mailbox_form.validate():
|
if not delete_mailbox_form.validate():
|
||||||
flash("Invalid request", "warning")
|
flash("Invalid request", "warning")
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
mailbox = Mailbox.get(delete_mailbox_form.mailbox_id.data)
|
try:
|
||||||
|
mailbox = delete_mailbox(
|
||||||
if not mailbox or mailbox.user_id != current_user.id:
|
current_user,
|
||||||
flash("Invalid mailbox. Refresh the page", "warning")
|
delete_mailbox_form.mailbox_id.data,
|
||||||
|
delete_mailbox_form.transfer_mailbox_id.data,
|
||||||
|
)
|
||||||
|
except MailboxError as e:
|
||||||
|
flash(str(e), "error")
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
if mailbox.id == current_user.default_mailbox_id:
|
|
||||||
flash("You cannot delete default mailbox", "error")
|
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
|
||||||
|
|
||||||
transfer_mailbox_id = delete_mailbox_form.transfer_mailbox_id.data
|
|
||||||
if transfer_mailbox_id and transfer_mailbox_id > 0:
|
|
||||||
transfer_mailbox = Mailbox.get(transfer_mailbox_id)
|
|
||||||
|
|
||||||
if not transfer_mailbox or transfer_mailbox.user_id != current_user.id:
|
|
||||||
flash(
|
|
||||||
"You must transfer the aliases to a mailbox you own.", "error"
|
|
||||||
)
|
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
|
||||||
|
|
||||||
if transfer_mailbox.id == mailbox.id:
|
|
||||||
flash(
|
|
||||||
"You can not transfer the aliases to the mailbox you want to delete.",
|
|
||||||
"error",
|
|
||||||
)
|
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
|
||||||
|
|
||||||
if not transfer_mailbox.verified:
|
|
||||||
flash("Your new mailbox is not verified", "error")
|
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
|
||||||
|
|
||||||
# Schedule delete account job
|
|
||||||
LOG.w(
|
|
||||||
f"schedule delete mailbox job for {mailbox.id} with transfer to mailbox {transfer_mailbox_id}"
|
|
||||||
)
|
|
||||||
Job.create(
|
|
||||||
name=JOB_DELETE_MAILBOX,
|
|
||||||
payload={
|
|
||||||
"mailbox_id": mailbox.id,
|
|
||||||
"transfer_mailbox_id": transfer_mailbox_id
|
|
||||||
if transfer_mailbox_id > 0
|
|
||||||
else None,
|
|
||||||
},
|
|
||||||
run_at=arrow.now(),
|
|
||||||
commit=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
flash(
|
flash(
|
||||||
f"Mailbox {mailbox.email} scheduled for deletion."
|
f"Mailbox {mailbox.email} scheduled for deletion."
|
||||||
f"You will receive a confirmation email when the deletion is finished",
|
f"You will receive a confirmation email when the deletion is finished",
|
||||||
|
@ -137,37 +98,23 @@ def mailbox_route():
|
||||||
if not current_user.is_premium():
|
if not current_user.is_premium():
|
||||||
flash("Only premium plan can add additional mailbox", "warning")
|
flash("Only premium plan can add additional mailbox", "warning")
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
mailbox_email = new_mailbox_form.email.data.lower().strip().replace(" ", "")
|
||||||
if new_mailbox_form.validate():
|
try:
|
||||||
mailbox_email = (
|
new_mailbox = create_mailbox_and_send_verification(
|
||||||
new_mailbox_form.email.data.lower().strip().replace(" ", "")
|
current_user, mailbox_email
|
||||||
)
|
)
|
||||||
|
flash(
|
||||||
if not is_valid_email(mailbox_email):
|
f"You are going to receive an email to confirm {mailbox_email}.",
|
||||||
flash(f"{mailbox_email} invalid", "error")
|
"success",
|
||||||
elif mailbox_already_used(mailbox_email, current_user):
|
)
|
||||||
flash(f"{mailbox_email} already used", "error")
|
return redirect(
|
||||||
elif not email_can_be_used_as_mailbox(mailbox_email):
|
url_for(
|
||||||
flash(f"You cannot use {mailbox_email}.", "error")
|
"dashboard.mailbox_detail_route",
|
||||||
else:
|
mailbox_id=new_mailbox.id,
|
||||||
new_mailbox = Mailbox.create(
|
|
||||||
email=mailbox_email, user_id=current_user.id
|
|
||||||
)
|
|
||||||
Session.commit()
|
|
||||||
|
|
||||||
send_verification_email(current_user, new_mailbox)
|
|
||||||
|
|
||||||
flash(
|
|
||||||
f"You are going to receive an email to confirm {mailbox_email}.",
|
|
||||||
"success",
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect(
|
|
||||||
url_for(
|
|
||||||
"dashboard.mailbox_detail_route",
|
|
||||||
mailbox_id=new_mailbox.id,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
except MailboxError as e:
|
||||||
|
flash(str(e), "error")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"dashboard/mailbox.html",
|
"dashboard/mailbox.html",
|
||||||
|
@ -178,30 +125,6 @@ def mailbox_route():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def send_verification_email(user, mailbox):
|
|
||||||
s = TimestampSigner(MAILBOX_SECRET)
|
|
||||||
mailbox_id_signed = s.sign(str(mailbox.id)).decode()
|
|
||||||
verification_url = (
|
|
||||||
URL + "/dashboard/mailbox_verify" + f"?mailbox_id={mailbox_id_signed}"
|
|
||||||
)
|
|
||||||
send_email(
|
|
||||||
mailbox.email,
|
|
||||||
f"Please confirm your mailbox {mailbox.email}",
|
|
||||||
render(
|
|
||||||
"transactional/verify-mailbox.txt.jinja2",
|
|
||||||
user=user,
|
|
||||||
link=verification_url,
|
|
||||||
mailbox_email=mailbox.email,
|
|
||||||
),
|
|
||||||
render(
|
|
||||||
"transactional/verify-mailbox.html",
|
|
||||||
user=user,
|
|
||||||
link=verification_url,
|
|
||||||
mailbox_email=mailbox.email,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dashboard_bp.route("/mailbox_verify")
|
@dashboard_bp.route("/mailbox_verify")
|
||||||
def mailbox_verify():
|
def mailbox_verify():
|
||||||
s = TimestampSigner(MAILBOX_SECRET)
|
s = TimestampSigner(MAILBOX_SECRET)
|
||||||
|
@ -218,8 +141,7 @@ def mailbox_verify():
|
||||||
flash("Invalid link", "error")
|
flash("Invalid link", "error")
|
||||||
return redirect(url_for("dashboard.mailbox_route"))
|
return redirect(url_for("dashboard.mailbox_route"))
|
||||||
|
|
||||||
mailbox.verified = True
|
set_mailbox_verified(mailbox)
|
||||||
Session.commit()
|
|
||||||
|
|
||||||
LOG.d("Mailbox %s is verified", mailbox)
|
LOG.d("Mailbox %s is verified", mailbox)
|
||||||
|
|
||||||
|
|
161
app/mailbox_utils.py
Normal file
161
app/mailbox_utils.py
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import secrets
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import arrow
|
||||||
|
from itsdangerous import TimestampSigner
|
||||||
|
|
||||||
|
from app import config
|
||||||
|
from app.config import JOB_DELETE_MAILBOX
|
||||||
|
from app.db import Session
|
||||||
|
from app.email_utils import (
|
||||||
|
is_valid_email,
|
||||||
|
mailbox_already_used,
|
||||||
|
email_can_be_used_as_mailbox,
|
||||||
|
send_email,
|
||||||
|
render,
|
||||||
|
)
|
||||||
|
from app.log import LOG
|
||||||
|
from app.models import User, Mailbox, Job
|
||||||
|
|
||||||
|
MAX_MAILBOX_VERIFICATION_TRIES = 3
|
||||||
|
|
||||||
|
|
||||||
|
class MailboxError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_new_validation_code_for_mailbox(mailbox: Mailbox) -> Mailbox:
|
||||||
|
mailbox.verification_code = secrets.randbelow(10**6)
|
||||||
|
mailbox.verification_expiration = arrow.utcnow().shift(minutes=15)
|
||||||
|
Session.commit()
|
||||||
|
return mailbox
|
||||||
|
|
||||||
|
|
||||||
|
def _send_verification_email(user, mailbox):
|
||||||
|
s = TimestampSigner(config.MAILBOX_SECRET)
|
||||||
|
mailbox_id_signed = s.sign(str(mailbox.id)).decode()
|
||||||
|
verification_url = (
|
||||||
|
config.URL + "/dashboard/mailbox_verify" + f"?mailbox_id={mailbox_id_signed}"
|
||||||
|
)
|
||||||
|
send_email(
|
||||||
|
mailbox.email,
|
||||||
|
f"Please confirm your mailbox {mailbox.email}",
|
||||||
|
render(
|
||||||
|
"transactional/verify-mailbox.txt.jinja2",
|
||||||
|
user=user,
|
||||||
|
link=verification_url,
|
||||||
|
mailbox=mailbox,
|
||||||
|
),
|
||||||
|
render(
|
||||||
|
"transactional/verify-mailbox.html",
|
||||||
|
user=user,
|
||||||
|
link=verification_url,
|
||||||
|
mailbox=mailbox,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_mailbox_and_send_verification(
|
||||||
|
user: User, email: str, use_code_validation: bool = False
|
||||||
|
) -> Mailbox:
|
||||||
|
mailbox_email = email.lower().strip().replace(" ", "")
|
||||||
|
|
||||||
|
if not is_valid_email(mailbox_email):
|
||||||
|
raise MailboxError(f"Invalid address {mailbox_email}")
|
||||||
|
elif mailbox_already_used(mailbox_email, user):
|
||||||
|
raise MailboxError(f"Mailbox {mailbox_email} already exists")
|
||||||
|
elif not email_can_be_used_as_mailbox(mailbox_email):
|
||||||
|
raise MailboxError(f"Invalid address {mailbox_email}")
|
||||||
|
|
||||||
|
new_mailbox = Mailbox.create(email=mailbox_email, user_id=user.id)
|
||||||
|
if use_code_validation:
|
||||||
|
new_mailbox.verification_tries = 0
|
||||||
|
new_mailbox = _attach_new_validation_code_for_mailbox(new_mailbox)
|
||||||
|
Session.commit()
|
||||||
|
_send_verification_email(user, new_mailbox)
|
||||||
|
|
||||||
|
return new_mailbox
|
||||||
|
|
||||||
|
|
||||||
|
def send_new_verification_to_mailbox(user: User, mailbox: Mailbox):
|
||||||
|
if mailbox.verified:
|
||||||
|
return
|
||||||
|
if mailbox.verification_tries > MAX_MAILBOX_VERIFICATION_TRIES:
|
||||||
|
mailbox.delete()
|
||||||
|
Session.commit()
|
||||||
|
return
|
||||||
|
mailbox = _attach_new_validation_code_for_mailbox(mailbox)
|
||||||
|
Session.commit()
|
||||||
|
_send_verification_email(user, mailbox)
|
||||||
|
|
||||||
|
|
||||||
|
def set_mailbox_verified(mailbox: Mailbox) -> Mailbox:
|
||||||
|
mailbox.verified = True
|
||||||
|
mailbox.verification_code = None
|
||||||
|
mailbox.verification_expiration = None
|
||||||
|
mailbox.verification_tries = 0
|
||||||
|
Session.commit()
|
||||||
|
return mailbox
|
||||||
|
|
||||||
|
|
||||||
|
def verify_mailbox_with_code(user: User, mailbox_id: int, code: str) -> Mailbox:
|
||||||
|
mailbox = Mailbox.get_by(id=mailbox_id)
|
||||||
|
if mailbox is None:
|
||||||
|
raise MailboxError("Invalid mailbox")
|
||||||
|
if mailbox.user_id != user.id:
|
||||||
|
raise MailboxError("Invalid mailbox")
|
||||||
|
if mailbox.verified:
|
||||||
|
return mailbox
|
||||||
|
if mailbox.verification_expiration < arrow.utcnow():
|
||||||
|
mailbox = _attach_new_validation_code_for_mailbox(mailbox)
|
||||||
|
_send_verification_email(user, mailbox)
|
||||||
|
raise MailboxError("Code has expired. A new one has been sent")
|
||||||
|
if mailbox.verification_code != code:
|
||||||
|
mailbox.verification_tries += 1
|
||||||
|
if mailbox.verification_tries >= MAX_MAILBOX_VERIFICATION_TRIES:
|
||||||
|
mailbox.delete()
|
||||||
|
Session.commit()
|
||||||
|
raise MailboxError("Too many tries")
|
||||||
|
raise MailboxError("Invalid code")
|
||||||
|
|
||||||
|
return set_mailbox_verified(mailbox)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_mailbox(
|
||||||
|
user: User, mailbox_id: int, transfer_mailbox_id: Optional[int] = None
|
||||||
|
) -> Mailbox:
|
||||||
|
mailbox = Mailbox.get(mailbox_id)
|
||||||
|
if mailbox is None:
|
||||||
|
raise MailboxError("Invalid mailbox")
|
||||||
|
if mailbox.user_id != user.id:
|
||||||
|
raise MailboxError("Invalid mailbox")
|
||||||
|
if mailbox.id == user.default_mailbox_id:
|
||||||
|
raise MailboxError("You cannot delete your default mailbox")
|
||||||
|
if transfer_mailbox_id and transfer_mailbox_id > 0:
|
||||||
|
transfer_mailbox = Mailbox.get(transfer_mailbox_id)
|
||||||
|
|
||||||
|
if not transfer_mailbox or transfer_mailbox.user_id != user.id:
|
||||||
|
raise MailboxError("You must transfer the aliases to a mailbox you own")
|
||||||
|
|
||||||
|
if transfer_mailbox.id == mailbox.id:
|
||||||
|
raise MailboxError(
|
||||||
|
"You can not transfer the aliases to the mailbox you want to delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not transfer_mailbox.verified:
|
||||||
|
raise MailboxError("Your new mailbox is not verified")
|
||||||
|
LOG.w(
|
||||||
|
f"Schedule delete mailbox job for {mailbox.id} with transfer to mailbox {transfer_mailbox_id}"
|
||||||
|
)
|
||||||
|
Job.create(
|
||||||
|
name=JOB_DELETE_MAILBOX,
|
||||||
|
payload={
|
||||||
|
"mailbox_id": mailbox.id,
|
||||||
|
"transfer_mailbox_id": transfer_mailbox_id
|
||||||
|
if transfer_mailbox_id is not None
|
||||||
|
else None,
|
||||||
|
},
|
||||||
|
run_at=arrow.now(),
|
||||||
|
commit=True,
|
||||||
|
)
|
||||||
|
return mailbox
|
|
@ -2517,6 +2517,11 @@ class Mailbox(Base, ModelMixin):
|
||||||
disabled = sa.Column(sa.Boolean, default=False, nullable=False, server_default="0")
|
disabled = sa.Column(sa.Boolean, default=False, nullable=False, server_default="0")
|
||||||
|
|
||||||
generic_subject = sa.Column(sa.String(78), nullable=True)
|
generic_subject = sa.Column(sa.String(78), nullable=True)
|
||||||
|
verification_code = sa.Column(sa.String(128), nullable=True)
|
||||||
|
verification_expiration = sa.Column(ArrowType, nullable=True)
|
||||||
|
verification_tries = sa.Column(
|
||||||
|
sa.Integer, nullable=False, default=0, server_default="0"
|
||||||
|
)
|
||||||
|
|
||||||
__table_args__ = (sa.UniqueConstraint("user_id", "email", name="uq_mailbox_user"),)
|
__table_args__ = (sa.UniqueConstraint("user_id", "email", name="uq_mailbox_user"),)
|
||||||
|
|
||||||
|
|
33
migrations/versions/2023_050911_f5f2e08fbd2e_.py
Normal file
33
migrations/versions/2023_050911_f5f2e08fbd2e_.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: f5f2e08fbd2e
|
||||||
|
Revises: 2634b41f54db
|
||||||
|
Create Date: 2023-05-09 11:52:41.100335
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy_utils
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'f5f2e08fbd2e'
|
||||||
|
down_revision = '2634b41f54db'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('mailbox', sa.Column('verification_code', sa.String(length=128), nullable=True))
|
||||||
|
op.add_column('mailbox', sa.Column('verification_expiration', sqlalchemy_utils.types.arrow.ArrowType(), nullable=True))
|
||||||
|
op.add_column('mailbox', sa.Column('verification_tries', sa.Integer(), server_default='0', nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('mailbox', 'verification_tries')
|
||||||
|
op.drop_column('mailbox', 'verification_expiration')
|
||||||
|
op.drop_column('mailbox', 'verification_code')
|
||||||
|
# ### end Alembic commands ###
|
120
static/package-lock.json
generated
vendored
120
static/package-lock.json
generated
vendored
|
@ -1,169 +1,123 @@
|
||||||
{
|
{
|
||||||
"name": "simplelogin",
|
"name": "simplelogin",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"dependencies": {
|
||||||
"": {
|
"@sentry/browser": {
|
||||||
"name": "simplelogin",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@sentry/browser": "^5.30.0",
|
|
||||||
"bootbox": "^5.5.3",
|
|
||||||
"font-awesome": "^4.7.0",
|
|
||||||
"htmx.org": "^1.6.1",
|
|
||||||
"intro.js": "^2.9.3",
|
|
||||||
"multiple-select": "^1.5.2",
|
|
||||||
"parsleyjs": "^2.9.2",
|
|
||||||
"qrious": "^4.0.2",
|
|
||||||
"toastr": "^2.1.4",
|
|
||||||
"vue": "^2.6.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sentry/browser": {
|
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.30.0.tgz",
|
||||||
"integrity": "sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw==",
|
"integrity": "sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"@sentry/core": "5.30.0",
|
"@sentry/core": "5.30.0",
|
||||||
"@sentry/types": "5.30.0",
|
"@sentry/types": "5.30.0",
|
||||||
"@sentry/utils": "5.30.0",
|
"@sentry/utils": "5.30.0",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/core": {
|
"@sentry/core": {
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz",
|
||||||
"integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==",
|
"integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"@sentry/hub": "5.30.0",
|
"@sentry/hub": "5.30.0",
|
||||||
"@sentry/minimal": "5.30.0",
|
"@sentry/minimal": "5.30.0",
|
||||||
"@sentry/types": "5.30.0",
|
"@sentry/types": "5.30.0",
|
||||||
"@sentry/utils": "5.30.0",
|
"@sentry/utils": "5.30.0",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/hub": {
|
"@sentry/hub": {
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz",
|
||||||
"integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==",
|
"integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"@sentry/types": "5.30.0",
|
"@sentry/types": "5.30.0",
|
||||||
"@sentry/utils": "5.30.0",
|
"@sentry/utils": "5.30.0",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/minimal": {
|
"@sentry/minimal": {
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz",
|
||||||
"integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==",
|
"integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"@sentry/hub": "5.30.0",
|
"@sentry/hub": "5.30.0",
|
||||||
"@sentry/types": "5.30.0",
|
"@sentry/types": "5.30.0",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/types": {
|
"@sentry/types": {
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz",
|
||||||
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==",
|
"integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw=="
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@sentry/utils": {
|
"@sentry/utils": {
|
||||||
"version": "5.30.0",
|
"version": "5.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz",
|
||||||
"integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
|
"integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"@sentry/types": "5.30.0",
|
"@sentry/types": "5.30.0",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bootbox": {
|
"bootbox": {
|
||||||
"version": "5.5.3",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/bootbox/-/bootbox-5.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/bootbox/-/bootbox-5.5.3.tgz",
|
||||||
"integrity": "sha512-B4mnm1DYgNHzoNtD7I0L/fixqvya4EEQy5bFF/yNmGI2Eq3WwVVwdfWf3hoF8KS+EaV4f0uIMqtxB1EAZwZPhQ==",
|
"integrity": "sha512-B4mnm1DYgNHzoNtD7I0L/fixqvya4EEQy5bFF/yNmGI2Eq3WwVVwdfWf3hoF8KS+EaV4f0uIMqtxB1EAZwZPhQ=="
|
||||||
"peerDependencies": {
|
|
||||||
"bootstrap": "^3.1.0 || ^4.4.0",
|
|
||||||
"jquery": "^3.5.1",
|
|
||||||
"popper.js": "^1.16.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/font-awesome": {
|
"font-awesome": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||||
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
|
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg=="
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.3"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/htmx.org": {
|
"htmx.org": {
|
||||||
"version": "1.6.1",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.7.0.tgz",
|
||||||
"integrity": "sha512-i+1k5ee2eFWaZbomjckyrDjUpa3FMDZWufatUSBmmsjXVksn89nsXvr1KLGIdAajiz+ZSL7TE4U/QaZVd2U2sA=="
|
"integrity": "sha512-wIQ3yNq7yiLTm+6BhV7Z8qKKTzEQv9xN/I4QsN5FvdGi69SNWTsSMlhH69HPa1rpZ8zSq1A/e7gTbTySxliP8g=="
|
||||||
},
|
},
|
||||||
"node_modules/intro.js": {
|
"intro.js": {
|
||||||
"version": "2.9.3",
|
"version": "2.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-2.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/intro.js/-/intro.js-2.9.3.tgz",
|
||||||
"integrity": "sha512-hC+EXWnEuJeA3CveGMat3XHePd2iaXNFJIVfvJh2E9IzBMGLTlhWvPIVHAgKlOpO4lNayCxEqzr4N02VmHFr9Q=="
|
"integrity": "sha512-hC+EXWnEuJeA3CveGMat3XHePd2iaXNFJIVfvJh2E9IzBMGLTlhWvPIVHAgKlOpO4lNayCxEqzr4N02VmHFr9Q=="
|
||||||
},
|
},
|
||||||
"node_modules/jquery": {
|
"jquery": {
|
||||||
"version": "3.5.1",
|
"version": "3.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz",
|
||||||
"integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
|
"integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ=="
|
||||||
},
|
},
|
||||||
"node_modules/multiple-select": {
|
"multiple-select": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/multiple-select/-/multiple-select-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/multiple-select/-/multiple-select-1.5.2.tgz",
|
||||||
"integrity": "sha512-sTNNRrjnTtB1b1+HTKcjQ/mjWY7Gvigo9F3C/3oTQCTFEpYzwaRYFPRAOu2SogfA1hEfyJTXjyS1VAbanJMsmA==",
|
"integrity": "sha512-sTNNRrjnTtB1b1+HTKcjQ/mjWY7Gvigo9F3C/3oTQCTFEpYzwaRYFPRAOu2SogfA1hEfyJTXjyS1VAbanJMsmA=="
|
||||||
"peerDependencies": {
|
|
||||||
"jquery": "1.9.1 - 3"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/parsleyjs": {
|
"parsleyjs": {
|
||||||
"version": "2.9.2",
|
"version": "2.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/parsleyjs/-/parsleyjs-2.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/parsleyjs/-/parsleyjs-2.9.2.tgz",
|
||||||
"integrity": "sha512-DKS2XXTjEUZ1BJWUzgXAr+550kFBZrom2WYweubqdV7WzdNC1hjOajZDfeBPoAZMkXumJPlB3v37IKatbiW8zQ==",
|
"integrity": "sha512-DKS2XXTjEUZ1BJWUzgXAr+550kFBZrom2WYweubqdV7WzdNC1hjOajZDfeBPoAZMkXumJPlB3v37IKatbiW8zQ==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"jquery": ">=1.8.0"
|
"jquery": ">=1.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qrious": {
|
"qrious": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz",
|
||||||
"integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g=="
|
"integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g=="
|
||||||
},
|
},
|
||||||
"node_modules/toastr": {
|
"toastr": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
|
||||||
"integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=",
|
"integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==",
|
||||||
"dependencies": {
|
"requires": {
|
||||||
"jquery": ">=1.12.0"
|
"jquery": ">=1.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"tslib": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
"node_modules/vue": {
|
"vue": {
|
||||||
"version": "2.6.14",
|
"version": "2.6.14",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
|
||||||
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
|
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
|
||||||
|
|
|
@ -3,9 +3,15 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{{ render_text("Hi") }}
|
{{ render_text("Hi") }}
|
||||||
{{ render_text("You have added <b>"+ mailbox_email +"</b> as an additional mailbox.") }}
|
{{ render_text("You have added <b>"+ mailbox.email +"</b> as an additional mailbox.") }}
|
||||||
{{ render_text("To confirm, please click on the button below.") }}
|
{% if mailbox.verification_code is not none -%}
|
||||||
{{ render_button("Confirm mailbox", link) }}
|
|
||||||
|
{{ render_text("To confirm, please write this code to verify") }}
|
||||||
|
{{ render_text("<strong>{}</strong>".format(mailbox.verification_code.zfill(6))) }}
|
||||||
|
{% else -%}
|
||||||
|
{{ render_text("To confirm, please click on the button below.") }}
|
||||||
|
{{ render_button("Confirm mailbox", link) }}
|
||||||
|
{% endif %}
|
||||||
{{ render_text("This email will only be valid for the next 15 minutes.") }}
|
{{ render_text("This email will only be valid for the next 15 minutes.") }}
|
||||||
{{ render_text('Thanks,
|
{{ render_text('Thanks,
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -3,11 +3,17 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
Hi
|
Hi
|
||||||
|
|
||||||
You have added {{mailbox_email}} as an additional mailbox.
|
You have added {{mailbox.email}} as an additional mailbox.
|
||||||
|
|
||||||
|
{% if mailbox.verification_code is not none -%}
|
||||||
|
To confirm, please write this code to verify:
|
||||||
|
|
||||||
|
{{ mailbox.verification_code.zfill(6) }}
|
||||||
|
{% else -%}
|
||||||
To confirm, please click on this link:
|
To confirm, please click on this link:
|
||||||
|
|
||||||
{{link}}
|
{{link}}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
This link will only be valid during the next 15 minutes.
|
This email will only be valid during the next 15 minutes.
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -28,7 +28,7 @@ def test_create_mailbox(flask_client):
|
||||||
)
|
)
|
||||||
|
|
||||||
assert r.status_code == 400
|
assert r.status_code == 400
|
||||||
assert r.json == {"error": "gmail.com invalid"}
|
assert r.json == {"error": "Invalid address gmail.com"}
|
||||||
|
|
||||||
|
|
||||||
def test_create_mailbox_fail_for_free_user(flask_client):
|
def test_create_mailbox_fail_for_free_user(flask_client):
|
||||||
|
|
165
tests/test_mailbox_utils.py
Normal file
165
tests/test_mailbox_utils.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import arrow
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy import desc
|
||||||
|
|
||||||
|
from app import config
|
||||||
|
from app.config import JOB_DELETE_MAILBOX
|
||||||
|
from app.db import Session
|
||||||
|
from app.mail_sender import mail_sender
|
||||||
|
from app.mailbox_utils import (
|
||||||
|
create_mailbox_and_send_verification,
|
||||||
|
verify_mailbox_with_code,
|
||||||
|
MailboxError,
|
||||||
|
delete_mailbox,
|
||||||
|
)
|
||||||
|
from app.models import Mailbox, Job
|
||||||
|
from tests.utils import create_new_user, random_email
|
||||||
|
|
||||||
|
test_user = None
|
||||||
|
|
||||||
|
|
||||||
|
def setup_module():
|
||||||
|
global test_user
|
||||||
|
config.SKIP_MX_LOOKUP_ON_CHECK = True
|
||||||
|
test_user = create_new_user()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_module():
|
||||||
|
config.SKIP_MX_LOOKUP_ON_CHECK = False
|
||||||
|
|
||||||
|
|
||||||
|
@mail_sender.store_emails_test_decorator
|
||||||
|
def test_mailbox_creation_sends_link_verification():
|
||||||
|
mailbox = create_mailbox_and_send_verification(test_user, random_email())
|
||||||
|
assert mailbox is not None
|
||||||
|
assert mailbox.verification_code is None
|
||||||
|
assert mailbox.verification_expiration is None
|
||||||
|
assert mailbox.verification_tries == 0
|
||||||
|
mails = mail_sender.get_stored_emails()
|
||||||
|
assert len(mails) == 1
|
||||||
|
assert "link" in str(mails[0].msg)
|
||||||
|
|
||||||
|
|
||||||
|
@mail_sender.store_emails_test_decorator
|
||||||
|
def test_mailbox_creation_sends_code_verification():
|
||||||
|
mailbox = create_mailbox_and_send_verification(test_user, random_email(), True)
|
||||||
|
assert mailbox is not None
|
||||||
|
assert mailbox.verification_code is not None
|
||||||
|
assert mailbox.verification_expiration is not None
|
||||||
|
assert mailbox.verification_tries == 0
|
||||||
|
mails = mail_sender.get_stored_emails()
|
||||||
|
assert len(mails) == 1
|
||||||
|
assert mailbox.verification_code in str(mails[0].msg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_verification_with_code():
|
||||||
|
mailbox = create_mailbox_and_send_verification(test_user, random_email(), True)
|
||||||
|
mailbox_id = mailbox.id
|
||||||
|
verified_mbox = verify_mailbox_with_code(
|
||||||
|
test_user, mailbox_id, mailbox.verification_code
|
||||||
|
)
|
||||||
|
assert verified_mbox.id == mailbox_id
|
||||||
|
assert verified_mbox.verified
|
||||||
|
assert verified_mbox.verification_code is None
|
||||||
|
assert verified_mbox.verification_expiration is None
|
||||||
|
assert verified_mbox.verification_tries == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_fail_verification_with_code():
|
||||||
|
mailbox = create_mailbox_and_send_verification(test_user, random_email(), True)
|
||||||
|
mailbox_id = mailbox.id
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
verify_mailbox_with_code(test_user, mailbox_id, "INVALID")
|
||||||
|
mbox = Mailbox.get_by(id=mailbox_id)
|
||||||
|
assert mbox.id == mailbox_id
|
||||||
|
assert not mbox.verified
|
||||||
|
assert mbox.verification_code is not None
|
||||||
|
assert mbox.verification_expiration is not None
|
||||||
|
assert mbox.verification_tries == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_fail_verification_with_invalid_mbox_id():
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
verify_mailbox_with_code(test_user, 99999999, "INVALID")
|
||||||
|
|
||||||
|
|
||||||
|
def test_verification_with_verified_mbox_is_ok():
|
||||||
|
mailbox = Mailbox.create(email=random_email(), user_id=test_user.id, verified=True)
|
||||||
|
Session.commit()
|
||||||
|
verified_mbox = verify_mailbox_with_code(test_user, mailbox.id, "INVALID")
|
||||||
|
assert verified_mbox.id == mailbox.id
|
||||||
|
|
||||||
|
|
||||||
|
@mail_sender.store_emails_test_decorator
|
||||||
|
def test_validate_expired_sends_a_new_email():
|
||||||
|
mailbox = Mailbox.create(
|
||||||
|
email=random_email(),
|
||||||
|
user_id=test_user.id,
|
||||||
|
verification_expiration=arrow.utcnow().shift(days=-1),
|
||||||
|
)
|
||||||
|
Session.commit()
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
verify_mailbox_with_code(test_user, mailbox.id, mailbox.verification_code)
|
||||||
|
sent_emails = mail_sender.get_stored_emails()
|
||||||
|
assert len(sent_emails) == 1
|
||||||
|
assert mailbox.verification_code in str(sent_emails[0].msg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_mailbox_without_transfer():
|
||||||
|
mailbox = Mailbox.create(email=random_email(), user_id=test_user.id, verified=True)
|
||||||
|
Session.commit()
|
||||||
|
delete_mailbox(test_user, mailbox.id)
|
||||||
|
job = (
|
||||||
|
Session.query(Job)
|
||||||
|
.filter_by(name=JOB_DELETE_MAILBOX)
|
||||||
|
.order_by(desc(Job.id))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
assert job.payload["mailbox_id"] == mailbox.id
|
||||||
|
assert job.payload["transfer_mailbox_id"] is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_mailbox_with_transfer():
|
||||||
|
mailbox = Mailbox.create(email=random_email(), user_id=test_user.id, verified=True)
|
||||||
|
transfer_mailbox = Mailbox.create(
|
||||||
|
email=random_email(), user_id=test_user.id, verified=True
|
||||||
|
)
|
||||||
|
Session.commit()
|
||||||
|
delete_mailbox(test_user, mailbox.id, transfer_mailbox.id)
|
||||||
|
job = (
|
||||||
|
Session.query(Job)
|
||||||
|
.filter_by(name=JOB_DELETE_MAILBOX)
|
||||||
|
.order_by(desc(Job.id))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
assert job.payload["mailbox_id"] == mailbox.id
|
||||||
|
assert job.payload["transfer_mailbox_id"] == transfer_mailbox.id
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_delete_primary_mailbox():
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
delete_mailbox(test_user, test_user.default_mailbox_id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_delete_another_users_mailbox():
|
||||||
|
other_user = create_new_user()
|
||||||
|
mailbox = Mailbox.create(email=random_email(), user_id=test_user.id, verified=True)
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
delete_mailbox(other_user, mailbox.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_delete_transfer_being_the_same():
|
||||||
|
mailbox = Mailbox.create(email=random_email(), user_id=test_user.id, verified=True)
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
delete_mailbox(test_user, mailbox.id, mailbox.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_transfer_to_another_user_mailbox():
|
||||||
|
other_user = create_new_user()
|
||||||
|
mailbox = Mailbox.create(email=random_email(), user_id=test_user.id, verified=True)
|
||||||
|
transfer_mailbox = Mailbox.create(
|
||||||
|
email=random_email(), user_id=other_user.id, verified=True
|
||||||
|
)
|
||||||
|
Session.commit()
|
||||||
|
with pytest.raises(MailboxError):
|
||||||
|
delete_mailbox(test_user, mailbox.id, transfer_mailbox.id)
|
Loading…
Reference in a new issue