def test_update_alias_name(flask_client):
    user = User.create(email="[email protected]",
                       password="******",
                       name="Test User",
                       activated=True)
    db.session.commit()

    # create api_key
    api_key = ApiKey.create(user.id, "for test")
    db.session.commit()

    alias = Alias.create_new_random(user)
    db.session.commit()

    r = flask_client.put(
        url_for("api.update_alias", alias_id=alias.id),
        headers={"Authentication": api_key.code},
        json={"name": "Test Name"},
    )
    assert r.status_code == 200
    alias = Alias.get(alias.id)
    assert alias.name == "Test Name"

    # update name with linebreak
    r = flask_client.put(
        url_for("api.update_alias", alias_id=alias.id),
        headers={"Authentication": api_key.code},
        json={"name": "Test \nName"},
    )
    assert r.status_code == 200
    alias = Alias.get(alias.id)
    assert alias.name == "Test Name"
Exemple #2
0
def test_alias_transfer(flask_client):
    user = login(flask_client)
    mb = Mailbox.create(user_id=user.id, email="*****@*****.**", commit=True)

    alias = Alias.create_new_random(user)
    db.session.commit()

    AliasMailbox.create(alias_id=alias.id, mailbox_id=mb.id, commit=True)

    new_user = User.create(
        email="*****@*****.**",
        password="******",
        activated=True,
        commit=True,
    )

    Mailbox.create(user_id=new_user.id,
                   email="*****@*****.**",
                   verified=True,
                   commit=True)

    alias_transfer.transfer(alias, new_user, new_user.mailboxes())

    # refresh from db
    alias = Alias.get(alias.id)
    assert alias.user == new_user
    assert set(alias.mailboxes) == set(new_user.mailboxes())
    assert len(alias.mailboxes) == 2
Exemple #3
0
def test_update_alias_mailboxes(flask_client):
    user = User.create(
        email="[email protected]", password="******", name="Test User", activated=True
    )
    db.session.commit()

    mb1 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True)
    mb2 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True)

    # create api_key
    api_key = ApiKey.create(user.id, "for test")
    db.session.commit()

    alias = Alias.create_new_random(user)
    db.session.commit()

    r = flask_client.put(
        url_for("api.update_alias", alias_id=alias.id),
        headers={"Authentication": api_key.code},
        json={"mailbox_ids": [mb1.id, mb2.id]},
    )

    assert r.status_code == 200
    alias = Alias.get(alias.id)

    assert alias.mailbox
    assert len(alias._mailboxes) == 1

    # fail when update with empty mailboxes
    r = flask_client.put(
        url_for("api.update_alias", alias_id=alias.id),
        headers={"Authentication": api_key.code},
        json={"mailbox_ids": []},
    )
    assert r.status_code == 400
Exemple #4
0
def get_alias_contacts_route(alias_id):
    """
    Get alias contacts
    Input:
        page_id: in query
    Output:
        - contacts: list of contacts:
            - creation_date
            - creation_timestamp
            - last_email_sent_date
            - last_email_sent_timestamp
            - contact
            - reverse_alias

    """
    user = g.user
    try:
        page_id = int(request.args.get("page_id"))
    except (ValueError, TypeError):
        return jsonify(error="page_id must be provided in request query"), 400

    alias: Alias = Alias.get(alias_id)

    if alias.user_id != user.id:
        return jsonify(error="Forbidden"), 403

    contacts = get_alias_contacts(alias, page_id)

    return jsonify(contacts=contacts), 200
def test_mailbox_delete(flask_client):
    user = User.create(email="[email protected]",
                       password="******",
                       name="Test User",
                       activated=True)
    db.session.commit()

    m1 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True)
    m2 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True)
    m3 = Mailbox.create(user_id=user.id, email="*****@*****.**", verified=True)
    db.session.commit()

    # alias has 2 mailboxes
    alias = Alias.create_new(user, "prefix", mailbox_id=m1.id)
    db.session.commit()

    alias._mailboxes.append(m2)
    alias._mailboxes.append(m3)
    db.session.commit()

    assert len(alias.mailboxes) == 3

    # delete m1, should not delete alias
    Mailbox.delete(m1.id)
    alias = Alias.get(alias.id)
    assert len(alias.mailboxes) == 2
Exemple #6
0
def alias_log(alias_id, page_id):
    alias = Alias.get(alias_id)

    # 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"))

    logs = get_alias_log(alias, page_id)
    base = (db.session.query(
        Contact, EmailLog).filter(Contact.id == EmailLog.contact_id).filter(
            Contact.alias_id == alias.id))
    total = base.count()
    email_forwarded = (base.filter(EmailLog.is_reply == False).filter(
        EmailLog.blocked == False).count())
    email_replied = base.filter(EmailLog.is_reply == True).count()
    email_blocked = base.filter(EmailLog.blocked == True).count()
    last_page = (len(logs) < PAGE_LIMIT
                 )  # lightweight pagination without counting all objects

    return render_template("dashboard/alias_log.html", **locals())
Exemple #7
0
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

    full_address: EmailAddress = address.parse(contact_addr)
    if not full_address:
        return jsonify(error=f"invalid contact email {contact_addr}"), 400

    contact_name, contact_email = full_address.display_name, full_address.address

    contact_email = sanitize_email(contact_email, not_lower=True)

    # 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

    try:
        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),
        )
    except CannotCreateContactForReverseAlias:
        return jsonify(
            error="You can't create contact for a reverse alias"), 400

    LOG.d("create reverse-alias for %s %s", contact_addr, alias)
    Session.commit()

    return jsonify(**serialize_contact(contact)), 201
Exemple #8
0
def handle_unsubscribe(envelope: Envelope):
    msg = email.message_from_bytes(envelope.original_content)

    # format: alias_id:
    subject = msg["Subject"]
    try:
        # subject has the format {alias.id}=
        if subject.endswith("="):
            alias_id = int(subject[:-1])
        # some email providers might strip off the = suffix
        else:
            alias_id = int(subject)

        alias = Alias.get(alias_id)
    except Exception:
        LOG.warning("Cannot parse alias from subject %s", msg["Subject"])
        return "550 SL E8"

    if not alias:
        LOG.warning("No such alias %s", alias_id)
        return "550 SL E9"

    # This sender cannot unsubscribe
    mail_from = envelope.mail_from.lower().strip()
    mailbox = Mailbox.get_by(user_id=alias.user_id, email=mail_from)
    if not mailbox or mailbox not in alias.mailboxes:
        LOG.d("%s cannot disable alias %s", envelope.mail_from, alias)
        return "550 SL E10"

    # Sender is owner of this alias
    alias.enabled = False
    db.session.commit()
    user = alias.user

    enable_alias_url = URL + f"/dashboard/?highlight_alias_id={alias.id}"
    for mailbox in alias.mailboxes:
        send_email(
            mailbox.email,
            f"Alias {alias.email} has been disabled successfully",
            render(
                "transactional/unsubscribe-disable-alias.txt",
                user=user,
                alias=alias.email,
                enable_alias_url=enable_alias_url,
            ),
            render(
                "transactional/unsubscribe-disable-alias.html",
                user=user,
                alias=alias.email,
                enable_alias_url=enable_alias_url,
            ),
        )

    return "250 Unsubscribe request accepted"
Exemple #9
0
def get_alias_activities(alias_id):
    """
    Get aliases
    Input:
        page_id: in query
    Output:
        - activities: list of activity:
            - from
            - to
            - timestamp
            - action: forward|reply|block|bounced
            - reverse_alias

    """
    user = g.user
    try:
        page_id = int(request.args.get("page_id"))
    except (ValueError, TypeError):
        return jsonify(error="page_id must be provided in request query"), 400

    alias: Alias = Alias.get(alias_id)

    if not alias or alias.user_id != user.id:
        return jsonify(error="Forbidden"), 403

    alias_logs = get_alias_log(alias, page_id)

    activities = []
    for alias_log in alias_logs:
        activity = {
            "timestamp": alias_log.when.timestamp,
            "reverse_alias": alias_log.reverse_alias,
            "reverse_alias_address": alias_log.contact.reply_email,
        }
        if alias_log.is_reply:
            activity["from"] = alias_log.alias
            activity["to"] = alias_log.website_email
            activity["action"] = "reply"
        else:
            activity["to"] = alias_log.alias
            activity["from"] = alias_log.website_email

            if alias_log.bounced:
                activity["action"] = "bounced"
            elif alias_log.blocked:
                activity["action"] = "block"
            else:
                activity["action"] = "forward"

        activities.append(activity)

    return jsonify(activities=activities), 200
Exemple #10
0
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
Exemple #11
0
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")

    # generate a reply_email, make sure it is unique
    # not use while to avoid infinite loop
    reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
    for _ in range(1000):
        reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
        if not Contact.get_by(reply_email=reply_email):
            break

    contact_name, contact_email = parseaddr_unicode(contact_addr)

    # already been added
    if Contact.get_by(alias_id=alias.id, website_email=contact_email):
        return jsonify(error="Contact already added"), 409

    contact = Contact.create(
        user_id=alias.user_id,
        alias_id=alias.id,
        website_email=contact_email,
        name=contact_name,
        reply_email=reply_email,
    )

    LOG.d("create reverse-alias for %s %s", contact_addr, alias)
    db.session.commit()

    return jsonify(**serialize_contact(contact)), 201
Exemple #12
0
def alias_transfer_receive_route():
    """
    URL has ?alias_id=signed_alias_id
    """
    s = Signer(ALIAS_TRANSFER_SECRET)
    signed_alias_id = request.args.get("alias_id")

    try:
        alias_id = int(s.unsign(signed_alias_id))
    except Exception:
        flash("Invalid link", "error")
        return redirect(url_for("dashboard.index"))
    else:
        alias = Alias.get(alias_id)

    # alias already belongs to this user
    if alias.user_id == current_user.id:
        flash("You already own this alias", "warning")
        return redirect(url_for("dashboard.index"))

    mailboxes = current_user.mailboxes()

    if request.method == "POST":
        mailbox_ids = request.form.getlist("mailbox_ids")
        # check if mailbox is not tempered with
        mailboxes = []
        for mailbox_id in mailbox_ids:
            mailbox = Mailbox.get(mailbox_id)
            if (not mailbox or mailbox.user_id != current_user.id
                    or not mailbox.verified):
                flash("Something went wrong, please retry", "warning")
                return redirect(request.url)
            mailboxes.append(mailbox)

        if not mailboxes:
            flash("You must select at least 1 mailbox", "warning")
            return redirect(request.url)

        LOG.d("transfer alias from %s to %s with %s", alias.user, current_user,
              mailboxes)
        transfer(alias, current_user, mailboxes)
        flash(f"You are now owner of {alias.email}", "success")
        return redirect(url_for("dashboard.index",
                                highlight_alias_id=alias.id))

    return render_template(
        "dashboard/alias_transfer_receive.html",
        alias=alias,
        mailboxes=mailboxes,
    )
Exemple #13
0
def get_alias(alias_id):
    """
    Get alias
    Input:
        alias_id: in url
    Output:
        Alias info, same as in get_aliases

    """
    user = g.user
    alias: Alias = Alias.get(alias_id)

    if alias.user_id != user.id:
        return jsonify(error="Forbidden"), 403

    return jsonify(**serialize_alias_info_v2(get_alias_info_v2(alias))), 200
Exemple #14
0
def delete_alias(alias_id):
    """
    Delete alias
    Input:
        alias_id: in url
    Output:
        200 if deleted successfully

    """
    user = g.user
    alias = Alias.get(alias_id)

    if not alias or alias.user_id != user.id:
        return jsonify(error="Forbidden"), 403

    alias_utils.delete_alias(alias, user)

    return jsonify(deleted=True), 200
Exemple #15
0
def alias_transfer_send_route(alias_id):
    alias = Alias.get(alias_id)
    if not alias or alias.user_id != current_user.id:
        flash("You cannot see this page", "warning")
        return redirect(url_for("dashboard.index"))

    if current_user.newsletter_alias_id == alias.id:
        flash(
            "This alias is currently used for receiving the newsletter and cannot be transferred",
            "error",
        )
        return redirect(url_for("dashboard.index"))

    if alias.transfer_token:
        alias_transfer_url = (URL + "/dashboard/alias_transfer/receive" +
                              f"?token={alias.transfer_token}")
    else:
        alias_transfer_url = None

    # generate a new transfer_token
    if request.method == "POST":
        if request.form.get("form-name") == "create":
            alias.transfer_token = str(uuid4())
            db.session.commit()
            alias_transfer_url = (URL + "/dashboard/alias_transfer/receive" +
                                  f"?token={alias.transfer_token}")
            flash("Share URL created", "success")
            return redirect(request.url)
        # request.form.get("form-name") == "remove"
        else:
            alias.transfer_token = None
            db.session.commit()
            alias_transfer_url = None
            flash("Share URL deleted", "success")
            return redirect(request.url)

    return render_template(
        "dashboard/alias_transfer_send.html",
        alias=alias,
        alias_transfer_url=alias_transfer_url,
    )
Exemple #16
0
def toggle_alias(alias_id):
    """
    Enable/disable alias
    Input:
        alias_id: in url
    Output:
        200 along with new status:
        - enabled


    """
    user = g.user
    alias: Alias = Alias.get(alias_id)

    if not alias or alias.user_id != user.id:
        return jsonify(error="Forbidden"), 403

    alias.enabled = not alias.enabled
    db.session.commit()

    return jsonify(enabled=alias.enabled), 200
def unsubscribe(alias_id):
    alias = Alias.get(alias_id)
    if not alias:
        flash("Incorrect link. Redirect you to the home page", "warning")
        return redirect(url_for("dashboard.index"))

    if alias.user_id != current_user.id:
        flash(
            "You don't have access to this page. Redirect you to the home page",
            "warning",
        )
        return redirect(url_for("dashboard.index"))

    # automatic unsubscribe, according to https://tools.ietf.org/html/rfc8058
    if request.method == "POST":
        alias.enabled = False
        flash(f"Alias {alias.email} has been blocked", "success")
        db.session.commit()

        return redirect(url_for("dashboard.index", highlight_alias_id=alias.id))
    else:  # ask user confirmation
        return render_template("dashboard/unsubscribe.html", alias=alias.email)
Exemple #18
0
def test_update_disable_pgp(flask_client):
    user = User.create(
        email="[email protected]", password="******", name="Test User", activated=True
    )
    db.session.commit()

    # create api_key
    api_key = ApiKey.create(user.id, "for test")
    db.session.commit()

    alias = Alias.create_new_random(user)
    db.session.commit()
    assert not alias.disable_pgp

    r = flask_client.put(
        url_for("api.update_alias", alias_id=alias.id),
        headers={"Authentication": api_key.code},
        json={"disable_pgp": True},
    )

    assert r.status_code == 200
    alias = Alias.get(alias.id)
    assert alias.disable_pgp
Exemple #19
0
def alias_transfer_send_route(alias_id):
    alias = Alias.get(alias_id)
    if not alias or alias.user_id != current_user.id:
        flash("You cannot see this page", "warning")
        return redirect(url_for("dashboard.index"))

    if current_user.newsletter_alias_id == alias.id:
        flash(
            "This alias is currently used for receiving the newsletter and cannot be transferred",
            "error",
        )
        return redirect(url_for("dashboard.index"))

    s = Signer(ALIAS_TRANSFER_SECRET)
    alias_id_signed = s.sign(str(alias.id)).decode()

    alias_transfer_url = (URL + "/dashboard/alias_transfer/receive" +
                          f"?alias_id={alias_id_signed}")

    return render_template(
        "dashboard/alias_transfer_send.html",
        alias=alias,
        alias_transfer_url=alias_transfer_url,
    )
Exemple #20
0
def update_alias(alias_id):
    """
    Update alias note
    Input:
        alias_id: in url
        note (optional): in body
        name (optional): in body
        mailbox_id (optional): in body
        disable_pgp (optional): in body
    Output:
        200
    """
    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 not alias or alias.user_id != user.id:
        return jsonify(error="Forbidden"), 403

    changed = False
    if "note" in data:
        new_note = data.get("note")
        alias.note = new_note
        changed = True

    if "mailbox_id" in data:
        mailbox_id = int(data.get("mailbox_id"))
        mailbox = Mailbox.get(mailbox_id)
        if not mailbox or mailbox.user_id != user.id or not mailbox.verified:
            return jsonify(error="Forbidden"), 400

        alias.mailbox_id = mailbox_id
        changed = True

    if "mailbox_ids" in data:
        mailbox_ids = [int(m_id) for m_id in data.get("mailbox_ids")]
        mailboxes: [Mailbox] = []

        # check if all mailboxes belong to user
        for mailbox_id in mailbox_ids:
            mailbox = Mailbox.get(mailbox_id)
            if not mailbox or mailbox.user_id != user.id or not mailbox.verified:
                return jsonify(error="Forbidden"), 400
            mailboxes.append(mailbox)

        if not mailboxes:
            return jsonify(error="Must choose at least one mailbox"), 400

        # <<< update alias mailboxes >>>
        # first remove all existing alias-mailboxes links
        AliasMailbox.query.filter_by(alias_id=alias.id).delete()
        db.session.flush()

        # then add all new mailboxes
        for i, mailbox in enumerate(mailboxes):
            if i == 0:
                alias.mailbox_id = mailboxes[0].id
            else:
                AliasMailbox.create(alias_id=alias.id, mailbox_id=mailbox.id)
        # <<< END update alias mailboxes >>>

        changed = True

    if "name" in data:
        new_name = data.get("name")
        alias.name = new_name
        changed = True

    if "disable_pgp" in data:
        alias.disable_pgp = data.get("disable_pgp")
        changed = True

    if "pinned" in data:
        alias.pinned = data.get("pinned")
        changed = True

    if changed:
        db.session.commit()

    return jsonify(ok=True), 200
Exemple #21
0
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,
    )
Exemple #22
0
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)

    # 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()

                # generate a reply_email, make sure it is unique
                # not use while to avoid infinite loop
                reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
                for _ in range(1000):
                    reply_email = f"ra+{random_string(25)}@{EMAIL_DOMAIN}"
                    if not Contact.get_by(reply_email=reply_email):
                        break

                try:
                    contact_name, contact_email = parseaddr_unicode(
                        contact_addr)
                except Exception:
                    flash(f"{contact_addr} is invalid", "error")
                    return redirect(
                        url_for(
                            "dashboard.alias_contact_manager",
                            alias_id=alias_id,
                        ))
                contact_email = contact_email.lower()

                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=reply_email,
                )

                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))

    # make sure highlighted contact is at array start
    contacts = alias.contacts

    if highlight_contact_id:
        contacts = sorted(contacts,
                          key=lambda fe: fe.id == highlight_contact_id,
                          reverse=True)

    return render_template(
        "dashboard/alias_contact_manager.html",
        contacts=contacts,
        alias=alias,
        new_contact_form=new_contact_form,
        highlight_contact_id=highlight_contact_id,
    )
Exemple #23
0
def index():
    query = request.args.get("query") or ""
    sort = request.args.get("sort") or ""
    alias_filter = request.args.get("filter") or ""

    page = 0
    if request.args.get("page"):
        page = int(request.args.get("page"))

    highlight_alias_id = None
    if request.args.get("highlight_alias_id"):
        highlight_alias_id = int(request.args.get("highlight_alias_id"))

    # User generates a new email
    if request.method == "POST":
        if request.form.get("form-name") == "create-custom-email":
            if current_user.can_create_new_alias():
                return redirect(url_for("dashboard.custom_alias"))
            else:
                flash(f"You need to upgrade your plan to create new alias.",
                      "warning")

        elif request.form.get("form-name") == "create-random-email":
            if current_user.can_create_new_alias():
                scheme = int(
                    request.form.get("generator_scheme")
                    or current_user.alias_generator)
                if not scheme or not AliasGeneratorEnum.has_value(scheme):
                    scheme = current_user.alias_generator
                alias = Alias.create_new_random(user=current_user,
                                                scheme=scheme)

                alias.mailbox_id = current_user.default_mailbox_id

                db.session.commit()

                LOG.d("generate new email %s for user %s", alias, current_user)
                flash(f"Alias {alias.email} has been created", "success")

                return redirect(
                    url_for(
                        "dashboard.index",
                        highlight_alias_id=alias.id,
                        query=query,
                        sort=sort,
                        filter=alias_filter,
                    ))
            else:
                flash(f"You need to upgrade your plan to create new alias.",
                      "warning")

        elif request.form.get("form-name") == "delete-email":
            alias_id = request.form.get("alias-id")
            alias: Alias = Alias.get(alias_id)
            if not alias:
                flash("Unknown error, sorry for the inconvenience", "error")
                return redirect(
                    url_for(
                        "dashboard.index",
                        highlight_alias_id=alias.id,
                        query=query,
                        sort=sort,
                        filter=alias_filter,
                    ))

            LOG.d("delete gen email %s", alias)
            email = alias.email
            alias_utils.delete_alias(alias, current_user)
            flash(f"Alias {email} has been deleted", "success")

        return redirect(
            url_for("dashboard.index",
                    query=query,
                    sort=sort,
                    filter=alias_filter))

    client_users = (ClientUser.filter_by(user_id=current_user.id).options(
        joinedload(ClientUser.client)).options(joinedload(
            ClientUser.alias)).all())

    sorted(client_users, key=lambda cu: cu.client.name)

    mailboxes = current_user.mailboxes()

    show_intro = False
    if not current_user.intro_shown:
        LOG.d("Show intro to %s", current_user)
        show_intro = True

        # to make sure not showing intro to user again
        current_user.intro_shown = True
        db.session.commit()

    stats = get_stats(current_user)

    alias_infos = get_alias_infos_with_pagination_v2(current_user, page, query,
                                                     sort, alias_filter)
    last_page = len(alias_infos) < PAGE_LIMIT

    return render_template(
        "dashboard/index.html",
        client_users=client_users,
        alias_infos=alias_infos,
        highlight_alias_id=highlight_alias_id,
        query=query,
        AliasGeneratorEnum=AliasGeneratorEnum,
        mailboxes=mailboxes,
        show_intro=show_intro,
        page=page,
        last_page=last_page,
        sort=sort,
        filter=alias_filter,
        stats=stats,
    )
Exemple #24
0
def index():
    query = request.args.get("query") or ""
    sort = request.args.get("sort") or ""
    alias_filter = request.args.get("filter") or ""

    page = 0
    if request.args.get("page"):
        page = int(request.args.get("page"))

    highlight_alias_id = None
    if request.args.get("highlight_alias_id"):
        try:
            highlight_alias_id = int(request.args.get("highlight_alias_id"))
        except ValueError:
            LOG.warning(
                "highlight_alias_id must be a number, received %s",
                request.args.get("highlight_alias_id"),
            )

    if request.method == "POST":
        if request.form.get("form-name") == "create-custom-email":
            if current_user.can_create_new_alias():
                return redirect(url_for("dashboard.custom_alias"))
            else:
                flash("You need to upgrade your plan to create new alias.",
                      "warning")

        elif request.form.get("form-name") == "create-random-email":
            if current_user.can_create_new_alias():
                scheme = int(
                    request.form.get("generator_scheme")
                    or current_user.alias_generator)
                if not scheme or not AliasGeneratorEnum.has_value(scheme):
                    scheme = current_user.alias_generator
                alias = Alias.create_new_random(user=current_user,
                                                scheme=scheme)

                alias.mailbox_id = current_user.default_mailbox_id

                db.session.commit()

                LOG.d("create new random alias %s for user %s", alias,
                      current_user)
                flash(f"Alias {alias.email} has been created", "success")

                return redirect(
                    url_for(
                        "dashboard.index",
                        highlight_alias_id=alias.id,
                        query=query,
                        sort=sort,
                        filter=alias_filter,
                    ))
            else:
                flash("You need to upgrade your plan to create new alias.",
                      "warning")

        elif request.form.get("form-name") in ("delete-alias",
                                               "disable-alias"):
            alias_id = request.form.get("alias-id")
            alias: Alias = Alias.get(alias_id)
            if not alias or alias.user_id != current_user.id:
                flash("Unknown error, sorry for the inconvenience", "error")
                return redirect(
                    url_for(
                        "dashboard.index",
                        query=query,
                        sort=sort,
                        filter=alias_filter,
                    ))

            if request.form.get("form-name") == "delete-alias":
                LOG.d("delete alias %s", alias)
                email = alias.email
                alias_utils.delete_alias(alias, current_user)
                flash(f"Alias {email} has been deleted", "success")
            elif request.form.get("form-name") == "disable-alias":
                alias.enabled = False
                db.session.commit()
                flash(f"Alias {alias.email} has been disabled", "success")

        return redirect(
            url_for("dashboard.index",
                    query=query,
                    sort=sort,
                    filter=alias_filter))

    mailboxes = current_user.mailboxes()

    show_intro = False
    if not current_user.intro_shown:
        LOG.d("Show intro to %s", current_user)
        show_intro = True

        # to make sure not showing intro to user again
        current_user.intro_shown = True
        db.session.commit()

    stats = get_stats(current_user)

    alias_infos = get_alias_infos_with_pagination_v3(current_user, page, query,
                                                     sort, alias_filter)
    last_page = len(alias_infos) < PAGE_LIMIT

    return render_template(
        "dashboard/index.html",
        alias_infos=alias_infos,
        highlight_alias_id=highlight_alias_id,
        query=query,
        AliasGeneratorEnum=AliasGeneratorEnum,
        mailboxes=mailboxes,
        show_intro=show_intro,
        page=page,
        last_page=last_page,
        sort=sort,
        filter=alias_filter,
        stats=stats,
    )
Exemple #25
0
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 = []
        elif r.status_code == 429:
            # rate limited
            LOG.w("HIBP rate limited, check alias %s in the next run", alias)
            await asyncio.sleep(1.6)
            return
        elif r.status_code > 500:
            LOG.w("HIBP server 5** error %s", r.status_code)
            return
        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()
        Session.add(alias)
        Session.commit()

        LOG.d("Updated breaches info for %s", alias)

        await asyncio.sleep(1.6)