def create_mailbox(): """ Create a new mailbox. User needs to verify the mailbox via an activation email. Input: email: in body Output: the new mailbox dict """ user = g.user mailbox_email = sanitize_email(request.get_json().get("email")) if not is_valid_email(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 ( jsonify( 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) db.session.commit() send_verification_email(user, new_mailbox) return ( jsonify(mailbox_to_dict(new_mailbox)), 201, )
def _check(form, field): email = field.data email = email.strip() email_part = email if "<" in email and ">" in email: if email.find("<") + 1 < email.find(">"): email_part = email[email.find("<") + 1:email.find(">")].strip() if not is_valid_email(email_part): raise ValidationError(message)
def test_is_valid_email(): assert is_valid_email("*****@*****.**") assert not is_valid_email("") assert not is_valid_email(" ") assert not is_valid_email("with [email protected]") assert not is_valid_email("strange char !ç@gmail.com") assert not is_valid_email("emoji👌@gmail.com")
def create_contact_route(alias_id): """ Create contact for an alias Input: alias_id: in url contact: in body Output: 201 if success 409 if contact already added """ data = request.get_json() if not data: return jsonify(error="request body cannot be empty"), 400 user = g.user alias: Alias = Alias.get(alias_id) if alias.user_id != user.id: return jsonify(error="Forbidden"), 403 contact_addr = data.get("contact") if not contact_addr: return jsonify(error="Contact cannot be empty"), 400 contact_name, contact_email = parseaddr_unicode(contact_addr) if not is_valid_email(contact_email): return jsonify(error=f"invalid contact email {contact_email}"), 400 contact_email = sanitize_email(contact_email) # already been added contact = Contact.get_by(alias_id=alias.id, website_email=contact_email) if contact: return jsonify(**serialize_contact(contact, existed=True)), 200 contact = Contact.create( user_id=alias.user_id, alias_id=alias.id, website_email=contact_email, name=contact_name, reply_email=generate_reply_email(contact_email, user), ) LOG.d("create reverse-alias for %s %s", contact_addr, alias) db.session.commit() return jsonify(**serialize_contact(contact)), 201
def sanity_check(): """ #TODO: investigate why DNS sometimes not working Different sanity checks - detect if there's mailbox that's using a invalid domain """ mailbox_ids = ( db.session.query(Mailbox.id) .filter(Mailbox.verified.is_(True), Mailbox.disabled.is_(False)) .all() ) mailbox_ids = [e[0] for e in mailbox_ids] # iterate over id instead of mailbox directly # as a mailbox can be deleted during the sleep time for mailbox_id in mailbox_ids: mailbox = Mailbox.get(mailbox_id) # a mailbox has been deleted if not mailbox: continue # hack to not query DNS too often sleep(1) if not email_can_be_used_as_mailbox(mailbox.email): mailbox.nb_failed_checks += 1 nb_email_log = nb_email_log_for_mailbox(mailbox) # send a warning if mailbox.nb_failed_checks == 5: if mailbox.user.email != mailbox.email: send_email( mailbox.user.email, f"Mailbox {mailbox.email} is disabled", render( "transactional/disable-mailbox-warning.txt", mailbox=mailbox ), render( "transactional/disable-mailbox-warning.html", mailbox=mailbox, ), ) # alert if too much fail and nb_email_log > 100 if mailbox.nb_failed_checks > 10 and nb_email_log > 100: mailbox.disabled = True if mailbox.user.email != mailbox.email: send_email( mailbox.user.email, f"Mailbox {mailbox.email} is disabled", render("transactional/disable-mailbox.txt", mailbox=mailbox), render("transactional/disable-mailbox.html", mailbox=mailbox), ) LOG.warning( "issue with mailbox %s domain. #alias %s, nb email log %s", mailbox, mailbox.nb_alias(), nb_email_log, ) else: # reset nb check mailbox.nb_failed_checks = 0 db.session.commit() for user in User.filter_by(activated=True).all(): if sanitize_email(user.email) != user.email: LOG.exception("%s does not have sanitized email", user) for alias in Alias.query.all(): if sanitize_email(alias.email) != alias.email: LOG.exception("Alias %s email not sanitized", alias) if alias.name and "\n" in alias.name: alias.name = alias.name.replace("\n", "") db.session.commit() LOG.exception("Alias %s name contains linebreak %s", alias, alias.name) contact_email_sanity_date = arrow.get("2021-01-12") for contact in Contact.query.all(): if sanitize_email(contact.reply_email) != contact.reply_email: LOG.exception("Contact %s reply-email not sanitized", contact) if ( sanitize_email(contact.website_email) != contact.website_email and contact.created_at > contact_email_sanity_date ): LOG.exception("Contact %s website-email not sanitized", contact) if not contact.invalid_email and not is_valid_email(contact.website_email): LOG.exception("%s invalid email", contact) contact.invalid_email = True db.session.commit() for mailbox in Mailbox.query.all(): if sanitize_email(mailbox.email) != mailbox.email: LOG.exception("Mailbox %s address not sanitized", mailbox) for contact in Contact.query.all(): if normalize_reply_email(contact.reply_email) != contact.reply_email: LOG.exception( "Contact %s reply email is not normalized %s", contact, contact.reply_email, ) for domain in CustomDomain.query.all(): if domain.name and "\n" in domain.name: LOG.exception("Domain %s name contain linebreak %s", domain, domain.name) migrate_domain_trash() set_custom_domain_for_alias() LOG.d("Finish sanity check")
def alias_contact_manager(alias_id): highlight_contact_id = None if request.args.get("highlight_contact_id"): highlight_contact_id = int(request.args.get("highlight_contact_id")) alias = Alias.get(alias_id) page = 0 if request.args.get("page"): page = int(request.args.get("page")) query = request.args.get("query") or "" # sanity check if not alias: flash("You do not have access to this page", "warning") return redirect(url_for("dashboard.index")) if alias.user_id != current_user.id: flash("You do not have access to this page", "warning") return redirect(url_for("dashboard.index")) new_contact_form = NewContactForm() if request.method == "POST": if request.form.get("form-name") == "create": if new_contact_form.validate(): contact_addr = new_contact_form.email.data.strip() try: contact_name, contact_email = parseaddr_unicode( contact_addr) contact_email = sanitize_email(contact_email) except Exception: flash(f"{contact_addr} is invalid", "error") return redirect( url_for( "dashboard.alias_contact_manager", alias_id=alias_id, )) if not is_valid_email(contact_email): flash(f"{contact_email} is invalid", "error") return redirect( url_for( "dashboard.alias_contact_manager", alias_id=alias_id, )) contact = Contact.get_by(alias_id=alias.id, website_email=contact_email) # already been added if contact: flash(f"{contact_email} is already added", "error") return redirect( url_for( "dashboard.alias_contact_manager", alias_id=alias_id, highlight_contact_id=contact.id, )) contact = Contact.create( user_id=alias.user_id, alias_id=alias.id, website_email=contact_email, name=contact_name, reply_email=generate_reply_email(contact_email, current_user), ) LOG.d("create reverse-alias for %s", contact_addr) db.session.commit() flash(f"Reverse alias for {contact_addr} is created", "success") return redirect( url_for( "dashboard.alias_contact_manager", alias_id=alias_id, highlight_contact_id=contact.id, )) elif request.form.get("form-name") == "delete": contact_id = request.form.get("contact-id") contact = Contact.get(contact_id) if not contact: flash("Unknown error. Refresh the page", "warning") return redirect( url_for("dashboard.alias_contact_manager", alias_id=alias_id)) elif contact.alias_id != alias.id: flash("You cannot delete reverse-alias", "warning") return redirect( url_for("dashboard.alias_contact_manager", alias_id=alias_id)) delete_contact_email = contact.website_email Contact.delete(contact_id) db.session.commit() flash(f"Reverse-alias for {delete_contact_email} has been deleted", "success") return redirect( url_for("dashboard.alias_contact_manager", alias_id=alias_id)) elif request.form.get("form-name") == "search": query = request.form.get("query") return redirect( url_for( "dashboard.alias_contact_manager", alias_id=alias_id, query=query, highlight_contact_id=highlight_contact_id, )) contact_infos = get_contact_infos(alias, page, query=query) last_page = len(contact_infos) < PAGE_LIMIT # if highlighted contact isn't included, fetch it # make sure highlighted contact is at array start contact_ids = [contact_info.contact.id for contact_info in contact_infos] if highlight_contact_id and highlight_contact_id not in contact_ids: contact_infos = (get_contact_infos( alias, contact_id=highlight_contact_id, query=query) + contact_infos) return render_template( "dashboard/alias_contact_manager.html", contact_infos=contact_infos, alias=alias, new_contact_form=new_contact_form, highlight_contact_id=highlight_contact_id, page=page, last_page=last_page, query=query, )
def mailbox_route(): mailboxes = ( Mailbox.query.filter_by(user_id=current_user.id) .order_by(Mailbox.created_at.desc()) .all() ) new_mailbox_form = NewMailboxForm() if request.method == "POST": if request.form.get("form-name") == "delete": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") 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")) email = mailbox.email Mailbox.delete(mailbox_id) db.session.commit() flash(f"Mailbox {email} has been deleted", "success") return redirect(url_for("dashboard.mailbox_route")) if request.form.get("form-name") == "set-default": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("This mailbox is already default one", "error") return redirect(url_for("dashboard.mailbox_route")) if not mailbox.verified: flash("Cannot set unverified mailbox as default", "error") return redirect(url_for("dashboard.mailbox_route")) current_user.default_mailbox_id = mailbox.id db.session.commit() flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success") return redirect(url_for("dashboard.mailbox_route")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add additional mailbox", "warning") return redirect(url_for("dashboard.mailbox_route")) if new_mailbox_form.validate(): mailbox_email = ( new_mailbox_form.email.data.lower().strip().replace(" ", "") ) if not is_valid_email(mailbox_email): flash(f"{mailbox_email} invalid", "error") elif mailbox_already_used(mailbox_email, current_user): flash(f"{mailbox_email} already used", "error") elif not email_can_be_used_as_mailbox(mailbox_email): flash(f"You cannot use {mailbox_email}.", "error") else: new_mailbox = Mailbox.create( email=mailbox_email, user_id=current_user.id ) db.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 ) ) return render_template( "dashboard/mailbox.html", mailboxes=mailboxes, new_mailbox_form=new_mailbox_form, )
def sanity_check(): LOG.d("sanitize user email") for user in User.filter_by(activated=True).all(): if sanitize_email(user.email) != user.email: LOG.e("%s does not have sanitized email", user) LOG.d("sanitize alias address & name") sanitize_alias_address_name() LOG.d("sanity contact address") contact_email_sanity_date = arrow.get("2021-01-12") for contact in Contact.yield_per_query(): if sanitize_email(contact.reply_email) != contact.reply_email: LOG.e("Contact %s reply-email not sanitized", contact) if (sanitize_email(contact.website_email, not_lower=True) != contact.website_email and contact.created_at > contact_email_sanity_date): LOG.e("Contact %s website-email not sanitized", contact) if not contact.invalid_email and not is_valid_email( contact.website_email): LOG.e("%s invalid email", contact) contact.invalid_email = True Session.commit() LOG.d("sanitize mailbox address") for mailbox in Mailbox.yield_per_query(): if sanitize_email(mailbox.email) != mailbox.email: LOG.e("Mailbox %s address not sanitized", mailbox) LOG.d("normalize reverse alias") for contact in Contact.yield_per_query(): if normalize_reply_email(contact.reply_email) != contact.reply_email: LOG.e( "Contact %s reply email is not normalized %s", contact, contact.reply_email, ) LOG.d("clean domain name") for domain in CustomDomain.yield_per_query(): if domain.name and "\n" in domain.name: LOG.e("Domain %s name contain linebreak %s", domain, domain.name) LOG.d("migrate domain trash if needed") migrate_domain_trash() LOG.d("fix custom domain for alias") set_custom_domain_for_alias() LOG.d("check mailbox valid domain") check_mailbox_valid_domain() LOG.d( """check if there's an email that starts with "\u200f" (right-to-left mark (RLM))""" ) for contact in (Contact.yield_per_query().filter( Contact.website_email.startswith("\u200f")).all()): contact.website_email = contact.website_email.replace("\u200f", "") LOG.e("remove right-to-left mark (RLM) from %s", contact) Session.commit() LOG.d("Finish sanity check")
def mailbox_route(): mailboxes = ( Mailbox.filter_by(user_id=current_user.id) .order_by(Mailbox.created_at.desc()) .all() ) new_mailbox_form = NewMailboxForm() if request.method == "POST": if request.form.get("form-name") == "delete": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") 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")) # Schedule delete account job LOG.w("schedule delete mailbox job for %s", mailbox) Job.create( name=JOB_DELETE_MAILBOX, payload={"mailbox_id": mailbox.id}, run_at=arrow.now(), commit=True, ) flash( f"Mailbox {mailbox.email} scheduled for deletion." f"You will receive a confirmation email when the deletion is finished", "success", ) return redirect(url_for("dashboard.mailbox_route")) if request.form.get("form-name") == "set-default": mailbox_id = request.form.get("mailbox-id") mailbox = Mailbox.get(mailbox_id) if not mailbox or mailbox.user_id != current_user.id: flash("Unknown error. Refresh the page", "warning") return redirect(url_for("dashboard.mailbox_route")) if mailbox.id == current_user.default_mailbox_id: flash("This mailbox is already default one", "error") return redirect(url_for("dashboard.mailbox_route")) if not mailbox.verified: flash("Cannot set unverified mailbox as default", "error") return redirect(url_for("dashboard.mailbox_route")) current_user.default_mailbox_id = mailbox.id Session.commit() flash(f"Mailbox {mailbox.email} is set as Default Mailbox", "success") return redirect(url_for("dashboard.mailbox_route")) elif request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add additional mailbox", "warning") return redirect(url_for("dashboard.mailbox_route")) if new_mailbox_form.validate(): mailbox_email = ( new_mailbox_form.email.data.lower().strip().replace(" ", "") ) if not is_valid_email(mailbox_email): flash(f"{mailbox_email} invalid", "error") elif mailbox_already_used(mailbox_email, current_user): flash(f"{mailbox_email} already used", "error") elif not email_can_be_used_as_mailbox(mailbox_email): flash(f"You cannot use {mailbox_email}.", "error") else: 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 ) ) return render_template( "dashboard/mailbox.html", mailboxes=mailboxes, new_mailbox_form=new_mailbox_form, )