From ab4f5bf329215725f287e6d677e74d52b697a744 Mon Sep 17 00:00:00 2001 From: Son NK Date: Tue, 3 Dec 2019 23:48:30 +0000 Subject: [PATCH] add /api/alias/options --- README.md | 32 +++++++++++++++ app/api/__init__.py | 2 +- app/api/base.py | 29 +++++++++++++- app/api/views/alias_options.py | 70 +++++++++++++++++++++++++++++++++ tests/api/__init__.py | 0 tests/api/test_alias_options.py | 55 ++++++++++++++++++++++++++ 6 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 app/api/views/alias_options.py create mode 100644 tests/api/__init__.py create mode 100644 tests/api/test_alias_options.py diff --git a/README.md b/README.md index 6e5a6659..97c3832b 100644 --- a/README.md +++ b/README.md @@ -74,4 +74,36 @@ response_type=id_token code return `id_token` in addition to `authorization_code` in /authorization endpoint +## API endpoints for extension + +``` +GET /alias/options hostname?="www.groupon.com" + recommendation?: + alias: www_groupon_com@simplelogin.co + hostname: www.groupon.com + + custom?: + suggestion: www_groupon_com + suffix: [@my_domain.com, .abcde@simplelogin.co] + + can_create_custom: true + can_create_random: true + + existing: + [email1, email2, ...] + +POST /alias/custom/new + prefix: www_groupon_com + suffix: @my_domain.com + + 201 -> OK {alias: "www_groupon_com@my_domain.com"} + 409 -> duplicated + +POST /alias/random/new + 201 -> OK {alias: "random_word@simplelogin.co"} + +``` + + + diff --git a/app/api/__init__.py b/app/api/__init__.py index 86bd305b..d3f61d00 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -1 +1 @@ -from .views import index +from .views import index, alias_options diff --git a/app/api/base.py b/app/api/base.py index 4b383883..c5021e74 100644 --- a/app/api/base.py +++ b/app/api/base.py @@ -1,3 +1,30 @@ -from flask import Blueprint +from functools import wraps + +import arrow +from flask import Blueprint, request, jsonify, g + +from app.extensions import db +from app.models import ApiKey api_bp = Blueprint(name="api", import_name=__name__, url_prefix="/api") + + +def verify_api_key(f): + @wraps(f) + def decorated(*args, **kwargs): + api_code = request.headers.get("Authentication") + api_key = ApiKey.get_by(code=api_code) + + if not api_key: + return jsonify(error="Wrong api key"), 401 + + # Update api key stats + api_key.last_used = arrow.now() + api_key.times += 1 + db.session.commit() + + g.user = api_key.user + + return f(*args, **kwargs) + + return decorated diff --git a/app/api/views/alias_options.py b/app/api/views/alias_options.py new file mode 100644 index 00000000..74e35c53 --- /dev/null +++ b/app/api/views/alias_options.py @@ -0,0 +1,70 @@ +from flask import jsonify, request, g +from flask_cors import cross_origin + +from app.api.base import api_bp, verify_api_key +from app.config import EMAIL_DOMAIN +from app.extensions import db +from app.log import LOG +from app.models import AliasUsedOn, GenEmail, User +from app.utils import random_string + + +@api_bp.route("/alias/options") +@cross_origin() +@verify_api_key +def options(): + """ + Return what options user has when creating new alias. + Input: + a valid api-key in "Authentication" header and + optional "hostname" in args + Output: cf README + optional recommendation: + optional custom + can_create_custom: boolean + can_create_random: boolean + existing: array of existing aliases + + """ + user = g.user + hostname = request.args.get("hostname") + + ret = { + "existing": [ge.email for ge in GenEmail.query.filter_by(user_id=user.id)], + "can_create_custom": user.can_create_new_custom_alias(), + "can_create_random": user.can_create_new_random_alias(), + } + + # recommendation alias if exist + if hostname: + q = db.session.query(AliasUsedOn, GenEmail, User).filter( + AliasUsedOn.gen_email_id == GenEmail.id, + GenEmail.user_id == user.id, + AliasUsedOn.hostname == hostname, + ) + + r = q.first() + if r: + _, alias, _ = r + LOG.d("found alias %s %s %s", alias, hostname, user) + ret["recommendation"] = {"alias": alias.email, "hostname": hostname} + + # custom alias suggestion and suffix + if user.can_create_new_custom_alias(): + ret["custom"] = {} + if hostname: + ret["custom"]["suggestion"] = hostname.replace(".", "_") + else: + ret["custom"]["suggestion"] = "" + + # maybe better to make sure the suffix is never used before + # but this is ok as there's a check when creating a new custom alias + ret["custom"]["suffixes"] = [f".{random_string(6)}@{EMAIL_DOMAIN}"] + + for custom_domain in user.verified_custom_domains(): + ret["custom"]["suffixes"].append("@" + custom_domain.domain) + + # custom domain should be put first + ret["custom"]["suffixes"] = list(reversed(ret["custom"]["suffixes"])) + + return jsonify(ret) diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/test_alias_options.py b/tests/api/test_alias_options.py new file mode 100644 index 00000000..0a083cae --- /dev/null +++ b/tests/api/test_alias_options.py @@ -0,0 +1,55 @@ +from flask import url_for + +from app.extensions import db +from app.models import User, ApiKey, AliasUsedOn, GenEmail + + +def test_different_scenarios(flask_client): + """Start with a blank database.""" + + # create user, user is not activated + user = User.create(email="a@b.c", password="password", name="Test User") + db.session.commit() + + # create api_key + api_key = ApiKey.create(user.id, "for test") + db.session.commit() + + # <<< without hostname >>> + r = flask_client.get( + url_for("api.options"), headers={"Authentication": api_key.code} + ) + + # { + # "can_create_custom": True, + # "can_create_random": True, + # "custom": {"suffix": ["azdwbw@sl.local"], "suggestion": ""}, + # "existing": ["cat_cat_cat@sl.local"], + # } + assert r.status_code == 200 + assert r.json["can_create_custom"] + assert r.json["can_create_random"] + assert len(r.json["existing"]) == 1 + assert r.json["custom"]["suffix"] + assert r.json["custom"]["suggestion"] == "" # no hostname => no suggestion + + # <<< with hostname >>> + r = flask_client.get( + url_for("api.options", hostname="www.test.com"), + headers={"Authentication": api_key.code}, + ) + + assert r.json["custom"]["suggestion"] == "www_test_com" + + # <<< with recommendation >>> + alias = GenEmail.create_new_gen_email(user.id) + db.session.commit() + AliasUsedOn.create(gen_email_id=alias.id, hostname="www.test.com") + db.session.commit() + + r = flask_client.get( + url_for("api.options", hostname="www.test.com"), + headers={"Authentication": api_key.code}, + ) + assert r.json["recommendation"]["alias"] == alias.email + assert r.json["recommendation"]["hostname"] == "www.test.com"