From 722bff319e1b19a5427056b7bfb66e6237e79d1e Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sat, 23 May 2020 16:09:06 +0200 Subject: [PATCH] add POST /api/mailboxes: create a new mailbox --- README.md | 17 ++++++++++ app/api/__init__.py | 1 + app/api/views/mailbox.py | 58 ++++++++++++++++++++++++++++++++++ app/dashboard/views/mailbox.py | 48 ++++++++++++++-------------- tests/api/test_mailbox.py | 31 ++++++++++++++++++ 5 files changed, 132 insertions(+), 23 deletions(-) create mode 100644 app/api/views/mailbox.py create mode 100644 tests/api/test_mailbox.py diff --git a/README.md b/README.md index 3d2a9707..a0520e13 100644 --- a/README.md +++ b/README.md @@ -1161,6 +1161,23 @@ List of mailboxes. Each mailbox has id, email field. } ``` +#### POST /api/mailboxes + +Create a new mailbox + +Input: +- `Authentication` header that contains the api key +- email: the new mailbox address + +Output: +- 201 along with the following response if new mailbox is created successfully. User is going to receive a verification email. + - id: integer + - email: the mailbox email address + - verified: boolean. + - default: whether is the default mailbox. User cannot delete the default mailbox +- 400 with error message otherwise. The error message can be displayed to user. + + ### Contact endpoints #### DELETE /api/contacts/:contact_id diff --git a/app/api/__init__.py b/app/api/__init__.py index a272e69c..71c76a3c 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -7,4 +7,5 @@ from .views import ( auth_mfa, alias, apple, + mailbox, ) diff --git a/app/api/views/mailbox.py b/app/api/views/mailbox.py new file mode 100644 index 00000000..faa68604 --- /dev/null +++ b/app/api/views/mailbox.py @@ -0,0 +1,58 @@ +from flask import g +from flask import jsonify +from flask import request +from flask_cors import cross_origin + +from app.api.base import api_bp, require_api_auth +from app.dashboard.views.mailbox import send_verification_email +from app.email_utils import ( + mailbox_already_used, + email_domain_can_be_used_as_mailbox, +) +from app.extensions import db +from app.models import Mailbox + + +@api_bp.route("/mailboxes", methods=["POST"]) +@cross_origin() +@require_api_auth +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 + - id + - email + - verified + + """ + user = g.user + mailbox_email = request.get_json().get("email").lower().strip() + + if mailbox_already_used(mailbox_email, user): + return jsonify(error=f"{mailbox_email} already used"), 400 + elif not email_domain_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( + id=new_mailbox.id, + email=new_mailbox.email, + verified=new_mailbox.verified, + default=user.default_mailbox_id == new_mailbox.id, + ), + 201, + ) diff --git a/app/dashboard/views/mailbox.py b/app/dashboard/views/mailbox.py index 8f1a99eb..6b0da7e3 100644 --- a/app/dashboard/views/mailbox.py +++ b/app/dashboard/views/mailbox.py @@ -94,29 +94,7 @@ def mailbox_route(): ) db.session.commit() - s = Signer(MAILBOX_SECRET) - mailbox_id_signed = s.sign(str(new_mailbox.id)).decode() - verification_url = ( - URL - + "/dashboard/mailbox_verify" - + f"?mailbox_id={mailbox_id_signed}" - ) - send_email( - mailbox_email, - f"Please confirm your email {mailbox_email}", - render( - "transactional/verify-mailbox.txt", - user=current_user, - link=verification_url, - mailbox_email=mailbox_email, - ), - render( - "transactional/verify-mailbox.html", - user=current_user, - link=verification_url, - mailbox_email=mailbox_email, - ), - ) + send_verification_email(current_user, new_mailbox) flash( f"You are going to receive an email to confirm {mailbox_email}.", @@ -138,6 +116,30 @@ def mailbox_route(): ) +def send_verification_email(user, mailbox): + s = Signer(MAILBOX_SECRET) + mailbox_id_signed = s.sign(str(mailbox.id)).decode() + verification_url = ( + URL + "/dashboard/mailbox_verify" + f"?mailbox_id={mailbox_id_signed}" + ) + send_email( + mailbox.email, + f"Please confirm your email {mailbox.email}", + render( + "transactional/verify-mailbox.txt", + user=user, + link=verification_url, + mailbox_email=mailbox.email, + ), + render( + "transactional/verify-mailbox.html", + user=user, + link=verification_url, + mailbox_email=mailbox.email, + ), + ) + + @dashboard_bp.route("/mailbox_verify") def mailbox_verify(): s = Signer(MAILBOX_SECRET) diff --git a/tests/api/test_mailbox.py b/tests/api/test_mailbox.py new file mode 100644 index 00000000..5751c65b --- /dev/null +++ b/tests/api/test_mailbox.py @@ -0,0 +1,31 @@ +from flask import url_for + +from flask import url_for + +from app.extensions import db +from app.models import User, ApiKey, Mailbox + + +def test_create_mailbox(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() + + r = flask_client.post( + url_for("api.create_mailbox"), + headers={"Authentication": api_key.code}, + json={"email": "mailbox@gmail.com"}, + ) + + assert r.status_code == 201 + assert r.json["email"] == "mailbox@gmail.com" + assert r.json["verified"] is False + assert r.json["id"] > 0 + assert r.json["default"] is False + +