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"
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
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
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
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())
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
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"
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
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 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
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, )
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
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
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, )
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)
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
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, )
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
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 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, )
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, )
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, )
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)