diff --git a/README.md b/README.md index 67359f1d..0ae02bdf 100644 --- a/README.md +++ b/README.md @@ -823,7 +823,7 @@ For ex: } ``` -#### POST /api/v2/alias/custom/new +#### POST /api/v3/alias/custom/new Create a new custom alias. @@ -833,6 +833,7 @@ Input: - Request Message Body in json (`Content-Type` is `application/json`) - alias_prefix: string. The first part of the alias that user can choose. - signed_suffix: should be one of the suffixes returned in the `GET /api/v4/alias/options` endpoint. + - mailboxes: list of mailbox_id that "owns" this alias - (Optional) note: alias note Output: diff --git a/app/api/views/new_custom_alias.py b/app/api/views/new_custom_alias.py index bcfef52c..353f95db 100644 --- a/app/api/views/new_custom_alias.py +++ b/app/api/views/new_custom_alias.py @@ -21,6 +21,8 @@ from app.models import ( CustomDomain, DeletedAlias, DomainDeletedAlias, + Mailbox, + AliasMailbox, ) from app.utils import convert_to_id @@ -187,3 +189,111 @@ def new_custom_alias_v2(): jsonify(alias=full_alias, **serialize_alias_info_v2(get_alias_info_v2(alias))), 201, ) + + +@api_bp.route("/v3/alias/custom/new", methods=["POST"]) +@cross_origin() +@require_api_auth +def new_custom_alias_v3(): + """ + Create a new custom alias + Same as v2 but accept a list of mailboxes as input + Input: + alias_prefix, for ex "www_groupon_com" + signed_suffix, either .random_letters@simplelogin.co or @my-domain.com + mailboxes: list of int + optional "hostname" in args + optional "note" + + Output: + 201 if success + 409 if the alias already exists + + """ + user: User = g.user + if not user.can_create_new_alias(): + LOG.d("user %s cannot create any custom alias", user) + return ( + jsonify( + error="You have reached the limitation of a free account with the maximum of " + f"{MAX_NB_EMAIL_FREE_PLAN} aliases, please upgrade your plan to create more aliases" + ), + 400, + ) + + hostname = request.args.get("hostname") + + data = request.get_json() + if not data: + return jsonify(error="request body cannot be empty"), 400 + + alias_prefix = data.get("alias_prefix", "").strip().lower() + signed_suffix = data.get("signed_suffix", "").strip() + mailbox_ids = data.get("mailboxes") + note = data.get("note") + alias_prefix = convert_to_id(alias_prefix) + + # 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 != user.id or not mailbox.verified: + return jsonify(error="Errors with Mailbox"), 400 + mailboxes.append(mailbox) + + if not mailboxes: + return jsonify(error="At least one mailbox must be selected"), 400 + + # hypothesis: user will click on the button in the 600 secs + try: + alias_suffix = signer.unsign(signed_suffix, max_age=600).decode() + except SignatureExpired: + LOG.error("Alias creation time expired for %s", user) + return jsonify(error="alias creation is expired, please try again"), 400 + except Exception: + LOG.error("Alias suffix is tampered, user %s", user) + return jsonify(error="Tampered suffix"), 400 + + if not verify_prefix_suffix(user, alias_prefix, alias_suffix): + return jsonify(error="wrong alias prefix or suffix"), 400 + + full_alias = alias_prefix + alias_suffix + if ( + Alias.get_by(email=full_alias) + or DeletedAlias.get_by(email=full_alias) + or DomainDeletedAlias.get_by(email=full_alias) + ): + LOG.d("full alias already used %s", full_alias) + return jsonify(error=f"alias {full_alias} already exists"), 409 + + custom_domain_id = None + if alias_suffix.startswith("@"): + alias_domain = alias_suffix[1:] + domain = CustomDomain.get_by(domain=alias_domain) + if domain: + custom_domain_id = domain.id + + alias = Alias.create( + user_id=user.id, + email=full_alias, + note=note, + mailbox_id=mailboxes[0].id, + custom_domain_id=custom_domain_id, + ) + db.session.flush() + + for i in range(1, len(mailboxes)): + AliasMailbox.create( + alias_id=alias.id, mailbox_id=mailboxes[i].id, + ) + + db.session.commit() + + if hostname: + AliasUsedOn.create(alias_id=alias.id, hostname=hostname, user_id=alias.user_id) + db.session.commit() + + return ( + jsonify(alias=full_alias, **serialize_alias_info_v2(get_alias_info_v2(alias))), + 201, + ) diff --git a/tests/api/test_new_custom_alias.py b/tests/api/test_new_custom_alias.py index 473def92..29417ab8 100644 --- a/tests/api/test_new_custom_alias.py +++ b/tests/api/test_new_custom_alias.py @@ -4,7 +4,7 @@ from app.alias_utils import delete_alias from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN from app.dashboard.views.custom_alias import signer from app.extensions import db -from app.models import User, ApiKey, Alias, CustomDomain +from app.models import User, ApiKey, Alias, CustomDomain, Mailbox from app.utils import random_word @@ -182,3 +182,53 @@ def test_cannot_create_alias_in_trash(flask_client): json={"alias_prefix": "prefix", "signed_suffix": suffix, "note": "test note",}, ) assert r.status_code == 409 + + +def test_success_v3(flask_client): + user = User.create( + email="a@b.c", password="password", name="Test User", activated=True, + ) + db.session.commit() + + # create api_key + api_key = ApiKey.create(user.id, "for test") + db.session.commit() + + # create another mailbox + mb = Mailbox.create(user_id=user.id, email="abcd@gmail.com", verified=True) + db.session.commit() + + # create new alias with note + word = random_word() + suffix = f".{word}@{EMAIL_DOMAIN}" + suffix = signer.sign(suffix).decode() + + r = flask_client.post( + url_for("api.new_custom_alias_v3", hostname="www.test.com"), + headers={"Authentication": api_key.code}, + json={ + "alias_prefix": "prefix", + "signed_suffix": suffix, + "note": "test note", + "mailboxes": [user.default_mailbox_id, mb.id], + }, + ) + + assert r.status_code == 201 + assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" + + # assert returned field + res = r.json + assert "id" in res + assert "email" in res + assert "creation_date" in res + assert "creation_timestamp" in res + assert "nb_forward" in res + assert "nb_block" in res + assert "nb_reply" in res + assert "enabled" in res + assert "note" in res + + new_alias: Alias = Alias.get_by(email=r.json["alias"]) + assert new_alias.note == "test note" + assert len(new_alias.mailboxes) == 2