Add related endpoints for registration

POST /api/auth/register
POST /api/auth/activate
POST /api/auth/reactivate
This commit is contained in:
Son NK 2020-02-28 19:01:54 +07:00
parent c025acc826
commit 32cd2fd650
5 changed files with 335 additions and 4 deletions

View File

@ -755,6 +755,35 @@ Input:
Output: Same output as for `/api/auth/login` endpoint
#### POST /api/auth/register
Input:
- email
- 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.
#### POST /api/auth/activate
Input:
- email
- code: the activation code
Output:
- 200: account is activated. User can login now
- 400: wrong email, code
- 410: wrong code too many times. User needs to ask for an reactivation -> next endpoint
#### POST /api/auth/reactivate
Input:
- email
Output:
- 200: user is going to receive an email that contains the activation code.
#### GET /api/aliases
Get user aliases.

View File

@ -1,4 +1,5 @@
from flask import jsonify, request
import random
import facebook
import google.oauth2.credentials
import googleapiclient.discovery
@ -12,10 +13,15 @@ from app.config import (
FLASK_SECRET,
DISABLE_REGISTRATION,
)
from app.email_utils import can_be_used_as_personal_email, email_already_used
from app.email_utils import (
can_be_used_as_personal_email,
email_already_used,
send_email,
render,
)
from app.extensions import db
from app.log import LOG
from app.models import User, ApiKey, SocialAuth
from app.models import User, ApiKey, SocialAuth, AccountActivation
@api_bp.route("/auth/login", methods=["POST"])
@ -55,6 +61,145 @@ def auth_login():
return jsonify(**auth_payload(user, device)), 200
@api_bp.route("/auth/register", methods=["POST"])
@cross_origin()
def auth_register():
"""
User signs up - will need to activate their account with an activation code.
Input:
email
password
Output:
200: user needs to confirm their account
"""
data = request.get_json()
if not data:
return jsonify(error="request body cannot be empty"), 400
email = data.get("email")
password = data.get("password")
if DISABLE_REGISTRATION:
return jsonify(error="registration is closed"), 400
if not can_be_used_as_personal_email(email) or email_already_used(email):
return jsonify(error=f"cannot use {email} as personal inbox"), 400
if not password or len(password) < 8:
return jsonify(error="password too short"), 400
LOG.debug("create user %s", email)
user = User.create(email=email, name="", password=password)
db.session.flush()
# create activation code
code = "".join([str(random.randint(0, 9)) for _ in range(6)])
AccountActivation.create(user_id=user.id, code=code)
db.session.commit()
send_email(
email,
f"Just one more step to join SimpleLogin",
render("transactional/code-activation.txt", code=code),
render("transactional/code-activation.html", code=code),
)
return jsonify(msg="User needs to confirm their account"), 200
@api_bp.route("/auth/activate", methods=["POST"])
@cross_origin()
def auth_activate():
"""
User enters the activation code to confirm their account.
Input:
email
code
Output:
200: user account is now activated, user can login now
400: wrong email, code
410: wrong code too many times
"""
data = request.get_json()
if not data:
return jsonify(error="request body cannot be empty"), 400
email = data.get("email")
code = data.get("code")
user = User.get_by(email=email)
# do not use a different message to avoid exposing existing email
if not user or user.activated:
return jsonify(error="Wrong email or code"), 400
account_activation = AccountActivation.get_by(user_id=user.id)
if not account_activation:
return jsonify(error="Wrong email or code"), 400
if account_activation.code != code:
# decrement nb tries
account_activation.tries -= 1
db.session.commit()
if account_activation.tries == 0:
AccountActivation.delete(account_activation.id)
db.session.commit()
return jsonify(error="Too many wrong tries"), 410
return jsonify(error="Wrong email or code"), 400
LOG.debug("activate user %s", user)
user.activated = True
AccountActivation.delete(account_activation.id)
db.session.commit()
return jsonify(msg="Account is activated, user can login now"), 200
@api_bp.route("/auth/reactivate", methods=["POST"])
@cross_origin()
def auth_reactivate():
"""
User asks for another activation code
Input:
email
Output:
200: user is going to receive an email for activate their account
"""
data = request.get_json()
if not data:
return jsonify(error="request body cannot be empty"), 400
email = data.get("email")
user = User.get_by(email=email)
# do not use a different message to avoid exposing existing email
if not user or user.activated:
return jsonify(error="Something went wrong"), 400
account_activation = AccountActivation.get_by(user_id=user.id)
if account_activation:
AccountActivation.delete(account_activation.id)
db.session.commit()
# create activation code
code = "".join([str(random.randint(0, 9)) for _ in range(6)])
AccountActivation.create(user_id=user.id, code=code)
db.session.commit()
send_email(
email,
f"Just one more step to join SimpleLogin",
render("transactional/code-activation.txt", code=code),
render("transactional/code-activation.html", code=code),
)
return jsonify(msg="User needs to confirm their account"), 200
@api_bp.route("/auth/facebook", methods=["POST"])
@cross_origin()
def auth_facebook():

View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
{{ render_text("Hi") }}
{{ render_text("Thank you for choosing SimpleLogin.") }}
{{ render_text("To get started, please activate your account by entering the following code into the application:") }}
{{ render_text("<h1>" + code + "</h1>")}}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
{% endblock %}

View File

@ -0,0 +1,10 @@
Hi,
Thank you for choosing SimpleLogin.
To get started, please activate your account by entering the following code into the application:
{{code}}
Thanks,
SimpleLogin Team.

View File

@ -1,7 +1,7 @@
from flask import url_for
from app.extensions import db
from app.models import User
from app.models import User, AccountActivation
def test_auth_login_success_mfa_disabled(flask_client):
@ -63,3 +63,140 @@ def test_auth_login_device_exist(flask_client):
json={"email": "a@b.c", "password": "password", "device": "Test Device"},
)
assert r.json["api_key"] == api_key
def test_auth_register_success(flask_client):
assert AccountActivation.get(1) is None
r = flask_client.post(
url_for("api.auth_register"), json={"email": "a@b.c", "password": "password"},
)
assert r.status_code == 200
assert r.json["msg"]
# make sure an activation code is created
act_code = AccountActivation.get(1)
assert act_code
assert len(act_code.code) == 6
assert act_code.tries == 3
def test_auth_register_too_short_password(flask_client):
r = flask_client.post(
url_for("api.auth_register"), json={"email": "a@b.c", "password": "short"},
)
assert r.status_code == 400
assert r.json["error"] == "password too short"
def test_auth_activate_success(flask_client):
r = flask_client.post(
url_for("api.auth_register"), json={"email": "a@b.c", "password": "password"},
)
assert r.status_code == 200
assert r.json["msg"]
# get the activation code
act_code = AccountActivation.get(1)
assert act_code
assert len(act_code.code) == 6
r = flask_client.post(
url_for("api.auth_activate"), json={"email": "a@b.c", "code": act_code.code},
)
assert r.status_code == 200
def test_auth_activate_wrong_email(flask_client):
r = flask_client.post(
url_for("api.auth_activate"), json={"email": "a@b.c", "code": "123456"},
)
assert r.status_code == 400
def test_auth_activate_user_already_activated(flask_client):
User.create(email="a@b.c", password="password", name="Test User", activated=True)
db.session.commit()
r = flask_client.post(
url_for("api.auth_activate"), json={"email": "a@b.c", "code": "123456"},
)
assert r.status_code == 400
def test_auth_activate_wrong_code(flask_client):
r = flask_client.post(
url_for("api.auth_register"), json={"email": "a@b.c", "password": "password"},
)
assert r.status_code == 200
assert r.json["msg"]
# get the activation code
act_code = AccountActivation.get(1)
assert act_code
assert len(act_code.code) == 6
assert act_code.tries == 3
# make sure to create a wrong code
wrong_code = act_code.code + "123"
r = flask_client.post(
url_for("api.auth_activate"), json={"email": "a@b.c", "code": wrong_code},
)
assert r.status_code == 400
# make sure the nb tries decrements
act_code = AccountActivation.get(1)
assert act_code.tries == 2
def test_auth_activate_too_many_wrong_code(flask_client):
r = flask_client.post(
url_for("api.auth_register"), json={"email": "a@b.c", "password": "password"},
)
assert r.status_code == 200
assert r.json["msg"]
# get the activation code
act_code = AccountActivation.get(1)
assert act_code
assert len(act_code.code) == 6
assert act_code.tries == 3
# make sure to create a wrong code
wrong_code = act_code.code + "123"
for _ in range(2):
r = flask_client.post(
url_for("api.auth_activate"), json={"email": "a@b.c", "code": wrong_code},
)
assert r.status_code == 400
# the activation code is deleted
r = flask_client.post(
url_for("api.auth_activate"), json={"email": "a@b.c", "code": wrong_code},
)
assert r.status_code == 410
# make sure the nb tries decrements
assert AccountActivation.get(1) is None
def test_auth_reactivate_success(flask_client):
User.create(email="a@b.c", password="password", name="Test User")
db.session.commit()
r = flask_client.post(url_for("api.auth_reactivate"), json={"email": "a@b.c"},)
assert r.status_code == 200
# make sure an activation code is created
act_code = AccountActivation.get(1)
assert act_code
assert len(act_code.code) == 6
assert act_code.tries == 3