From 72e9b52b292278c2c074e3fcbb8545d5c2107c49 Mon Sep 17 00:00:00 2001 From: Son NK <> Date: Sat, 2 May 2020 16:21:18 +0200 Subject: [PATCH] Add /api/v4/alias/options --- README.md | 21 +++++----- app/api/views/alias_options.py | 69 +++++++++++++++++++++++++++++++++ tests/api/test_alias_options.py | 48 +++++++++++++++++++++++ 3 files changed, 128 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f07f2fe4..4646cecb 100644 --- a/README.md +++ b/README.md @@ -659,7 +659,7 @@ Output: if api key is correct, return a json with user name and whether user is If api key is incorrect, return 401. -#### GET /api/v3/alias/options +#### GET /api/v4/alias/options User alias info and suggestion. Used by the first extension screen when user opens the extension. @@ -669,7 +669,7 @@ Input: Output: a json with the following field: - can_create: boolean. Whether user can create new alias -- suffixes: list of string. List of alias `suffix` that user can use. If user doesn't have custom domain, this list has a single element which is the alias default domain (simplelogin.co). +- suffixes: list of `[suffix, signed-suffix]`. List of alias `suffix` that user can use. The `signed-suffix` is necessary to avoid request tampering. - prefix_suggestion: string. Suggestion for the `alias prefix`. Usually this is the website name extracted from `hostname`. If no `hostname`, then the `prefix_suggestion` is empty. - recommendation: optional field, dictionary. If an alias is already used for this website, the recommendation will be returned. There are 2 subfields in `recommendation`: `alias` which is the recommended alias and `hostname` is the website on which this alias is used before. @@ -677,15 +677,16 @@ For ex: ```json { "can_create": true, - "prefix_suggestion": "test", - "recommendation": { - "alias": "e1.cat@sl.local", - "hostname": "www.test.com" - }, + "prefix_suggestion": "", "suffixes": [ - "@very-long-domain.com.net.org", - "@ab.cd", - ".cat@sl.local" + [ + "@ab.cd", + "@ab.cd.Xq2BOA.zBebBB-QYikFkbPZ9CPKGpJ2-PU" + ], + [ + ".yeah@local1.localhost", + ".yeah@local1.localhost.Xq2BOA.dM9gyHyHcSXuJ8ps4i3wpJZ_Frw" + ] ] } ``` diff --git a/app/api/views/alias_options.py b/app/api/views/alias_options.py index 92ed46c9..eb0c938f 100644 --- a/app/api/views/alias_options.py +++ b/app/api/views/alias_options.py @@ -4,6 +4,7 @@ from sqlalchemy import desc from app.api.base import api_bp, require_api_auth from app.config import ALIAS_DOMAINS, DISABLE_ALIAS_SUFFIX +from app.dashboard.views.custom_alias import available_suffixes from app.extensions import db from app.log import LOG from app.models import AliasUsedOn, Alias, User @@ -239,3 +240,71 @@ def options_v3(): ret["suffixes"] = list(reversed(ret["suffixes"])) return jsonify(ret) + + +@api_bp.route("/v4/alias/options") +@cross_origin() +@require_api_auth +def options_v4(): + """ + Return what options user has when creating new alias. + Same as v3 but return time-based signed-suffix in addition to suffix. To be used with /v2/alias/custom/new + Input: + a valid api-key in "Authentication" header and + optional "hostname" in args + Output: cf README + can_create: bool + suffixes: [[suffix, signed_suffix]] + prefix_suggestion: str + recommendation: Optional dict + alias: str + hostname: str + + + """ + user = g.user + hostname = request.args.get("hostname") + + ret = { + "can_create": user.can_create_new_alias(), + "suffixes": [], + "prefix_suggestion": "", + } + + # recommendation alias if exist + if hostname: + # put the latest used alias first + q = ( + db.session.query(AliasUsedOn, Alias, User) + .filter( + AliasUsedOn.alias_id == Alias.id, + Alias.user_id == user.id, + AliasUsedOn.hostname == hostname, + ) + .order_by(desc(AliasUsedOn.created_at)) + ) + + 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 hostname: + # keep only the domain name of hostname, ignore TLD and subdomain + # for ex www.groupon.com -> groupon + domain_name = hostname + if "." in hostname: + parts = hostname.split(".") + domain_name = parts[-2] + domain_name = convert_to_id(domain_name) + ret["prefix_suggestion"] = domain_name + + # List of (is_custom_domain, alias-suffix, time-signed alias-suffix) + suffixes = available_suffixes(user) + + # custom domain should be put first + ret["suffixes"] = list([suffix[1], suffix[2]] for suffix in suffixes) + + return jsonify(ret) diff --git a/tests/api/test_alias_options.py b/tests/api/test_alias_options.py index 904b2ad3..539a21d3 100644 --- a/tests/api/test_alias_options.py +++ b/tests/api/test_alias_options.py @@ -143,3 +143,51 @@ def test_different_scenarios_v3(flask_client): ) assert r.json["recommendation"]["alias"] == alias.email assert r.json["recommendation"]["hostname"] == "www.test.com" + + +def test_different_scenarios_v4(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() + + # <<< without hostname >>> + r = flask_client.get( + url_for("api.options_v4"), headers={"Authentication": api_key.code} + ) + + assert r.status_code == 200 + + assert r.json["can_create"] + assert r.json["suffixes"] + assert r.json["prefix_suggestion"] == "" # no hostname => no suggestion + + for (suffix, signed_suffix) in r.json["suffixes"]: + assert signed_suffix.startswith(suffix) + + # <<< with hostname >>> + r = flask_client.get( + url_for("api.options_v4", hostname="www.test.com"), + headers={"Authentication": api_key.code}, + ) + + assert r.json["prefix_suggestion"] == "test" + + # <<< with recommendation >>> + alias = Alias.create_new(user, prefix="test") + db.session.commit() + AliasUsedOn.create( + alias_id=alias.id, hostname="www.test.com", user_id=alias.user_id + ) + db.session.commit() + + r = flask_client.get( + url_for("api.options_v4", hostname="www.test.com"), + headers={"Authentication": api_key.code}, + ) + assert r.json["recommendation"]["alias"] == alias.email + assert r.json["recommendation"]["hostname"] == "www.test.com"