Merge pull request #104 from simple-login/api-note

Support alias note in API
This commit is contained in:
Son Nguyen Kim 2020-03-11 12:28:45 +01:00 committed by GitHub
commit 9bcec60736
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 98 additions and 27 deletions

View file

@ -67,7 +67,7 @@ docker run --name sl -it --rm \
Then open http://localhost:7777, you should be able to login with `john@wick.com/password` account! Then open http://localhost:7777, you should be able to login with `john@wick.com/password` account!
To use SimpleLogin aliases, you need to deploy it on your server with some DNS setup though, To use SimpleLogin aliases, you need to deploy it on your server with some DNS setup though,
the following section will show a step-by-step guide on how to get your own email forwarder service! the following section will show a step-by-step guide on how to get your own email forwarder service!
# Table of Contents # Table of Contents
@ -156,7 +156,7 @@ An **A record** that points `app.mydomain.com.` to your server IP. To verify, th
```bash ```bash
dig @1.1.1.1 app.mydomain.com a dig @1.1.1.1 app.mydomain.com a
``` ```
should return your server IP. should return your server IP.
@ -194,7 +194,7 @@ To verify, the following command
```bash ```bash
dig @1.1.1.1 dkim._domainkey.mydomain.com txt dig @1.1.1.1 dkim._domainkey.mydomain.com txt
``` ```
should return the above value. should return the above value.
@ -209,10 +209,10 @@ Add a TXT record for `mydomain.com.` with the value:
``` ```
v=spf1 mx -all v=spf1 mx -all
``` ```
What it means is only your server can send email with `@mydomain.com` domain. What it means is only your server can send email with `@mydomain.com` domain.
To verify, the following command To verify, the following command
```bash ```bash
dig @1.1.1.1 mydomain.com txt dig @1.1.1.1 mydomain.com txt
@ -241,7 +241,7 @@ To verify, the following command
dig @1.1.1.1 _dmarc.mydomain.com txt dig @1.1.1.1 _dmarc.mydomain.com txt
``` ```
should return the set value. should return the set value.
For more information on DMARC, please consult https://tools.ietf.org/html/rfc7489 For more information on DMARC, please consult https://tools.ietf.org/html/rfc7489
@ -272,7 +272,7 @@ sudo docker network create -d bridge \
### Postgres ### Postgres
This section creates a Postgres database using Docker. This section creates a Postgres database using Docker.
If you already have a Postgres database in use, you can skip this section and just copy the database configuration (i.e. host, port, username, password, database name) to use in the next sections. If you already have a Postgres database in use, you can skip this section and just copy the database configuration (i.e. host, port, username, password, database name) to use in the next sections.
@ -376,7 +376,7 @@ smtpd_recipient_restrictions =
permit permit
``` ```
Create the `/etc/postfix/pgsql-relay-domains.cf` file with the following content. Create the `/etc/postfix/pgsql-relay-domains.cf` file with the following content.
Make sure that the database config is correctly set and replace `mydomain.com` with your domain. Make sure that the database config is correctly set and replace `mydomain.com` with your domain.
``` ```
@ -386,11 +386,11 @@ user = myuser
password = mypassword password = mypassword
dbname = simplelogin dbname = simplelogin
query = SELECT domain FROM custom_domain WHERE domain='%s' AND verified=true query = SELECT domain FROM custom_domain WHERE domain='%s' AND verified=true
UNION SELECT '%s' WHERE '%s' = 'mydomain.com' LIMIT 1; UNION SELECT '%s' WHERE '%s' = 'mydomain.com' LIMIT 1;
``` ```
Create the `/etc/postfix/pgsql-transport-maps.cf` file with the following content. Create the `/etc/postfix/pgsql-transport-maps.cf` file with the following content.
Again, make sure that the database config is correctly set and replace `mydomain.com` with your domain. Again, make sure that the database config is correctly set and replace `mydomain.com` with your domain.
``` ```
@ -401,7 +401,7 @@ password = mypassword
dbname = simplelogin dbname = simplelogin
# forward to smtp:127.0.0.1:20381 for custom domain AND email domain # forward to smtp:127.0.0.1:20381 for custom domain AND email domain
query = SELECT 'smtp:127.0.0.1:20381' FROM custom_domain WHERE domain = '%s' AND verified=true query = SELECT 'smtp:127.0.0.1:20381' FROM custom_domain WHERE domain = '%s' AND verified=true
UNION SELECT 'smtp:127.0.0.1:20381' WHERE '%s' = 'mydomain.com' LIMIT 1; UNION SELECT 'smtp:127.0.0.1:20381' WHERE '%s' = 'mydomain.com' LIMIT 1;
``` ```
@ -539,7 +539,7 @@ You could make a donation to SimpleLogin on our Patreon page at https://www.patr
The above self-hosting instructions correspond to a freshly Ubuntu server and doesn't cover all possible server configuration. The above self-hosting instructions correspond to a freshly Ubuntu server and doesn't cover all possible server configuration.
Below are pointers to different topics: Below are pointers to different topics:
- [UFW - uncomplicated firewall](docs/ufw.md) - [UFW - uncomplicated firewall](docs/ufw.md)
- [SES - Amazon Simple Email Service](docs/ses.md) - [SES - Amazon Simple Email Service](docs/ses.md)
## Contributing ## Contributing
@ -560,7 +560,7 @@ Then make sure all tests pass
pytest pytest
``` ```
Install npm packages Install npm packages
```bash ```bash
cd static && npm install cd static && npm install
@ -680,6 +680,7 @@ Input:
- Request Message Body in json (`Content-Type` is `application/json`) - Request Message Body in json (`Content-Type` is `application/json`)
- alias_prefix: string. The first part of the alias that user can choose. - alias_prefix: string. The first part of the alias that user can choose.
- alias_suffix: should be one of the suffixes returned in the `GET /api/v2/alias/options` endpoint. - alias_suffix: should be one of the suffixes returned in the `GET /api/v2/alias/options` endpoint.
- (Optional) note: alias note
Output: Output:
If success, 201 with the new alias, for example If success, 201 with the new alias, for example
@ -698,6 +699,8 @@ Input:
- `Authentication` header that contains the api key - `Authentication` header that contains the api key
- (Optional but recommended) `hostname` passed in query string - (Optional but recommended) `hostname` passed in query string
- (Optional) mode: either `uuid` or `word`. By default, use the user setting when creating new random alias. - (Optional) mode: either `uuid` or `word`. By default, use the user setting when creating new random alias.
- Request Message Body in json (`Content-Type` is `application/json`)
- (Optional) note: alias note
Output: Output:
If success, 201 with the new alias, for example If success, 201 with the new alias, for example
@ -760,7 +763,7 @@ Output: Same output as for `/api/auth/login` endpoint
Input: Input:
- email - email
- password - password
Output: 200 means user is going to receive an email that contains an *activation code*. User needs to enter this code to confirm their account -> next endpoint. Output: 200 means user is going to receive an email that contains an *activation code*. User needs to enter this code to confirm their account -> next endpoint.
@ -769,7 +772,7 @@ Output: 200 means user is going to receive an email that contains an *activation
Input: Input:
- email - email
- code: the activation code - code: the activation code
Output: Output:
- 200: account is activated. User can login now - 200: account is activated. User can login now
@ -782,7 +785,7 @@ Input:
- email - email
Output: Output:
- 200: user is going to receive an email that contains the activation code. - 200: user is going to receive an email that contains the activation code.
#### GET /api/aliases #### GET /api/aliases
@ -806,7 +809,8 @@ If success, 200 with the list of aliases, for example:
"nb_block": 0, "nb_block": 0,
"nb_forward": 0, "nb_forward": 0,
"nb_reply": 0, "nb_reply": 0,
"enabled": true "enabled": true,
"note": "This is a note"
}, },
{ {
"creation_date": "2020-02-04 16:23:02+00:00", "creation_date": "2020-02-04 16:23:02+00:00",
@ -816,7 +820,8 @@ If success, 200 with the list of aliases, for example:
"nb_block": 0, "nb_block": 0,
"nb_forward": 0, "nb_forward": 0,
"nb_reply": 0, "nb_reply": 0,
"enabled": false "enabled": false,
"note": null
} }
] ]
} }
@ -828,7 +833,7 @@ Delete an alias
Input: Input:
- `Authentication` header that contains the api key - `Authentication` header that contains the api key
- `alias_id` in url. - `alias_id` in url.
Output: Output:
If success, 200. If success, 200.
@ -846,7 +851,7 @@ Enable/disable alias
Input: Input:
- `Authentication` header that contains the api key - `Authentication` header that contains the api key
- `alias_id` in url. - `alias_id` in url.
Output: Output:
If success, 200 along with the new alias status: If success, 200 along with the new alias status:

View file

@ -26,6 +26,7 @@ def get_aliases():
- nb_forward - nb_forward
- nb_block - nb_block
- nb_reply - nb_reply
- note
""" """
user = g.user user = g.user
@ -48,6 +49,7 @@ def get_aliases():
"nb_block": alias.nb_blocked, "nb_block": alias.nb_blocked,
"nb_reply": alias.nb_reply, "nb_reply": alias.nb_reply,
"enabled": alias.gen_email.enabled, "enabled": alias.gen_email.enabled,
"note": alias.note,
} }
for alias in aliases for alias in aliases
] ]

View file

@ -21,6 +21,7 @@ def new_custom_alias():
alias_prefix, for ex "www_groupon_com" alias_prefix, for ex "www_groupon_com"
alias_suffix, either .random_letters@simplelogin.co or @my-domain.com alias_suffix, either .random_letters@simplelogin.co or @my-domain.com
optional "hostname" in args optional "hostname" in args
optional "note"
Output: Output:
201 if success 201 if success
409 if the alias already exists 409 if the alias already exists
@ -46,6 +47,7 @@ def new_custom_alias():
alias_prefix = data.get("alias_prefix", "").strip() alias_prefix = data.get("alias_prefix", "").strip()
alias_suffix = data.get("alias_suffix", "").strip() alias_suffix = data.get("alias_suffix", "").strip()
note = data.get("note")
alias_prefix = convert_to_id(alias_prefix) alias_prefix = convert_to_id(alias_prefix)
if not verify_prefix_suffix(user, alias_prefix, alias_suffix, user_custom_domains): if not verify_prefix_suffix(user, alias_prefix, alias_suffix, user_custom_domains):
@ -57,7 +59,7 @@ def new_custom_alias():
return jsonify(error=f"alias {full_alias} already exists"), 409 return jsonify(error=f"alias {full_alias} already exists"), 409
gen_email = GenEmail.create( gen_email = GenEmail.create(
user_id=user.id, email=full_alias, mailbox_id=user.default_mailbox_id user_id=user.id, email=full_alias, mailbox_id=user.default_mailbox_id, note=note
) )
db.session.commit() db.session.commit()

View file

@ -15,6 +15,8 @@ from app.models import GenEmail, AliasUsedOn, AliasGeneratorEnum
def new_random_alias(): def new_random_alias():
""" """
Create a new random alias Create a new random alias
Input:
(Optional) note
Output: Output:
201 if success 201 if success
@ -30,6 +32,11 @@ def new_random_alias():
400, 400,
) )
note = None
data = request.get_json()
if data:
note = data.get("note")
scheme = user.alias_generator scheme = user.alias_generator
mode = request.args.get("mode") mode = request.args.get("mode")
if mode: if mode:
@ -40,7 +47,7 @@ def new_random_alias():
else: else:
return jsonify(error=f"{mode} must be either word or alias"), 400 return jsonify(error=f"{mode} must be either word or alias"), 400
gen_email = GenEmail.create_new_random(user=user, scheme=scheme) gen_email = GenEmail.create_new_random(user=user, scheme=scheme, note=note)
db.session.commit() db.session.commit()
hostname = request.args.get("hostname") hostname = request.args.get("hostname")

View file

@ -30,6 +30,7 @@ class AliasInfo:
show_intro_test_send_email: bool = False show_intro_test_send_email: bool = False
highlight: bool = False highlight: bool = False
note: str
def __init__(self, **kwargs): def __init__(self, **kwargs):
for k, v in kwargs.items(): for k, v in kwargs.items():
@ -230,6 +231,7 @@ def get_alias_info(
nb_reply=0, nb_reply=0,
highlight=ge.id == highlight_gen_email_id, highlight=ge.id == highlight_gen_email_id,
mailbox=mb, mailbox=mb,
note=ge.note,
) )
alias_info = aliases[ge.email] alias_info = aliases[ge.email]

View file

@ -581,12 +581,19 @@ class GenEmail(db.Model, ModelMixin):
@classmethod @classmethod
def create_new_random( def create_new_random(
cls, user, scheme: int = AliasGeneratorEnum.word.value, in_hex: bool = False cls,
user,
scheme: int = AliasGeneratorEnum.word.value,
in_hex: bool = False,
note: str = None,
): ):
"""create a new random alias""" """create a new random alias"""
random_email = generate_email(scheme=scheme, in_hex=in_hex) random_email = generate_email(scheme=scheme, in_hex=in_hex)
return GenEmail.create( return GenEmail.create(
user_id=user.id, email=random_email, mailbox_id=user.default_mailbox_id user_id=user.id,
email=random_email,
mailbox_id=user.default_mailbox_id,
note=note,
) )
def mailbox_email(self): def mailbox_email(self):

View file

@ -48,6 +48,18 @@ def test_success_with_pagination(flask_client):
assert r.status_code == 200 assert r.status_code == 200
assert len(r.json["aliases"]) == PAGE_LIMIT assert len(r.json["aliases"]) == PAGE_LIMIT
# assert returned field
for a in r.json["aliases"]:
assert "id" in a
assert "email" in a
assert "creation_date" in a
assert "creation_timestamp" in a
assert "nb_forward" in a
assert "nb_block" in a
assert "nb_reply" in a
assert "enabled" in a
assert "note" in a
# get aliases on the 2nd page, should return 2 aliases # get aliases on the 2nd page, should return 2 aliases
# as the total number of aliases is PAGE_LIMIT +2 # as the total number of aliases is PAGE_LIMIT +2
# 1 alias is created when user is created # 1 alias is created when user is created

View file

@ -16,17 +16,38 @@ def test_success(flask_client):
api_key = ApiKey.create(user.id, "for test") api_key = ApiKey.create(user.id, "for test")
db.session.commit() db.session.commit()
# create new alias with note
word = random_word() word = random_word()
r = flask_client.post( r = flask_client.post(
url_for("api.new_custom_alias", hostname="www.test.com"), url_for("api.new_custom_alias", hostname="www.test.com"),
headers={"Authentication": api_key.code}, headers={"Authentication": api_key.code},
json={"alias_prefix": "prefix", "alias_suffix": f".{word}@{EMAIL_DOMAIN}"}, json={
"alias_prefix": "prefix",
"alias_suffix": f".{word}@{EMAIL_DOMAIN}",
"note": "test note",
},
) )
assert r.status_code == 201 assert r.status_code == 201
assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}"
new_ge = GenEmail.get_by(email=r.json["alias"])
assert new_ge.note == "test note"
# create alias without note
word = random_word()
r = flask_client.post(
url_for("api.new_custom_alias", hostname="www.test.com"),
headers={"Authentication": api_key.code},
json={"alias_prefix": "prefix", "alias_suffix": f".{word}@{EMAIL_DOMAIN}",},
)
assert r.status_code == 201
assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}"
new_ge = GenEmail.get_by(email=r.json["alias"])
assert new_ge.note is None
def test_out_of_quota(flask_client): def test_out_of_quota(flask_client):
user = User.create( user = User.create(

View file

@ -36,6 +36,7 @@ def test_custom_mode(flask_client):
api_key = ApiKey.create(user.id, "for test") api_key = ApiKey.create(user.id, "for test")
db.session.commit() db.session.commit()
# without note
r = flask_client.post( r = flask_client.post(
url_for("api.new_random_alias", hostname="www.test.com", mode="uuid"), url_for("api.new_random_alias", hostname="www.test.com", mode="uuid"),
headers={"Authentication": api_key.code}, headers={"Authentication": api_key.code},
@ -47,6 +48,18 @@ def test_custom_mode(flask_client):
uuid_part = alias[: len(alias) - len(EMAIL_DOMAIN) - 1] uuid_part = alias[: len(alias) - len(EMAIL_DOMAIN) - 1]
assert is_valid_uuid(uuid_part) assert is_valid_uuid(uuid_part)
# with note
r = flask_client.post(
url_for("api.new_random_alias", hostname="www.test.com", mode="uuid"),
headers={"Authentication": api_key.code},
json={"note": "test note",},
)
assert r.status_code == 201
alias = r.json["alias"]
ge = GenEmail.get_by(email=alias)
assert ge.note == "test note"
def test_out_of_quota(flask_client): def test_out_of_quota(flask_client):
user = User.create( user = User.create(