about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2023-01-05 07:53:21 +0300
committerFrederick Muriuki Muriithi2023-01-05 07:53:21 +0300
commit93e20e7be957c0a4c8b08d2a5395f29109865c82 (patch)
tree8c527e968eef756671bffdfe0e2b119eb6042158
parent3752c663fa9ded1801680aa2342947b7676d8ce9 (diff)
downloadgenenetwork3-93e20e7be957c0a4c8b08d2a5395f29109865c82.tar.gz
auth: Persist the user's registration details
If the registration details pass the validations steps, then persist the
details in the database and respond with details about the newly created user.

* gn3/auth/authentication/users.py: new functions
  * `save_user`: create and persist a new user
  * `set_user_password`: hash and persist the new password for the given user
* gn3/auth/authorisation/views.py: Persist the user details and respond with
  the newly persisted user details.
-rw-r--r--gn3/auth/authentication/users.py26
-rw-r--r--gn3/auth/authorisation/views.py38
2 files changed, 54 insertions, 10 deletions
diff --git a/gn3/auth/authentication/users.py b/gn3/auth/authentication/users.py
index 6ec6bca..0cd5852 100644
--- a/gn3/auth/authentication/users.py
+++ b/gn3/auth/authentication/users.py
@@ -1,5 +1,5 @@
 """User-specific code and data structures."""
-from uuid import UUID
+from uuid import UUID, uuid4
 from typing import NamedTuple
 
 import bcrypt
@@ -53,3 +53,27 @@ def valid_login(conn: db.DbConnection, user: User, password: str) -> bool:
         return False
 
     return bcrypt.checkpw(password.encode("utf-8"), row["password"])
+
+def save_user(cursor: db.DbCursor, email: str, name: str) -> User:
+    """
+    Create and persist a user.
+
+    The user creation could be done during a transaction, therefore the function
+    takes a cursor object rather than a connection.
+
+    The newly created and persisted user is then returned.
+    """
+    user_id = uuid4()
+    cursor.execute("INSERT INTO users VALUES (?, ?, ?)",
+                   (str(user_id), email, name))
+    return User(user_id, email, name)
+
+def set_user_password(
+        cursor: db.DbCursor, user: User, password: str) -> Tuple[User, bytes]:
+    """Set the given user's password in the database."""
+    hashed_password = bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt())
+    cursor.execute(
+        ("INSERT INTO user_credentials VALUES (:user_id, :hash) "
+         "ON CONFLICT (user_id) DO UPDATE SET password=:hash"),
+        {"user_id": str(user.user_id), "hash": hashed_password})
+    return user, hashed_password
diff --git a/gn3/auth/authorisation/views.py b/gn3/auth/authorisation/views.py
index 73d39d2..6ac3be0 100644
--- a/gn3/auth/authorisation/views.py
+++ b/gn3/auth/authorisation/views.py
@@ -8,7 +8,9 @@ from gn3.auth.blueprint import oauth2
 from .groups import user_group
 from .errors import UserRegistrationError
 from .roles import user_roles as _user_roles
+
 from ..authentication.oauth2.resource_server import require_oauth
+from ..authentication.users import User, save_user, set_user_password
 from ..authentication.oauth2.models.oauth2token import token_by_access_token
 
 @oauth2.route("/user", methods=["GET"])
@@ -41,6 +43,8 @@ def __email_valid__(email: str) -> Tuple[bool, Optional[str]]:
         return False, "Empty email address"
 
     ## Check that the address is a valid email address
+    ## Review use of `email-validator` or `pyIsEmail` python packages for
+    ## validating the emails, if it turns out this is important.
 
     ## Success
     return True, None
@@ -54,6 +58,12 @@ def __password_valid__(password, confirm_password) -> Tuple[bool, Optional[str]]
 
     return True, None
 
+def __user_name_valid__(name: str) -> Tuple[bool, Optional[str]]:
+    if name == "":
+        return False, "User's name not provided."
+
+    return True, None
+
 def __assert_not_logged_in__(conn: db.DbConnection):
     bearer = request.headers.get('Authorization')
     if bearer:
@@ -70,17 +80,27 @@ def register_user():
         __assert_not_logged_in__(conn)
 
         form = request.form
+        email = form.get("email", "")
+        password = form.get("password", "")
+        user_name = form.get("user_name", "")
         errors = tuple(
                 error[1] for error in
-            [__email_valid__(form.get("email", "")),
-             __password_valid__(form.get("password", ""),
-                                form.get("confirm_password", ""))]
+            [__email_valid__(email),
+             __password_valid__(password, form.get("confirm_password", "")),
+             __user_name_valid__(user_name)]
             if error[0])
         if len(errors) > 0:
             raise UserRegistrationError(*errors)
-        # Provide default privileges
-        return jsonify(
-            {
-                "error": "not_implemented",
-                "error_description": "Feature not implemented"
-             }), 500
+
+        with db.cursor(conn) as cursor:
+            user, _hashed_password = set_user_password(
+                cursor, save_user(cursor, email, user_name), password)
+            return jsonify(
+                {
+                    "user_id": user.user_id,
+                    "email": user.email,
+                    "name": user.name
+                }), 200
+
+    raise Exception(
+        "unknown_error", "The system experienced an unexpected error.")