about summary refs log tree commit diff
path: root/gn3/auth/authentication
diff options
context:
space:
mode:
Diffstat (limited to 'gn3/auth/authentication')
-rw-r--r--gn3/auth/authentication/__init__.py24
-rw-r--r--gn3/auth/authentication/exceptions.py4
-rw-r--r--gn3/auth/authentication/oauth2/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/introspection.py48
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/revocation.py22
-rw-r--r--gn3/auth/authentication/oauth2/endpoints/utilities.py31
-rw-r--r--gn3/auth/authentication/oauth2/grants/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py85
-rw-r--r--gn3/auth/authentication/oauth2/grants/password_grant.py22
-rw-r--r--gn3/auth/authentication/oauth2/models/__init__.py0
-rw-r--r--gn3/auth/authentication/oauth2/models/authorization_code.py93
-rw-r--r--gn3/auth/authentication/oauth2/models/oauth2client.py234
-rw-r--r--gn3/auth/authentication/oauth2/models/oauth2token.py133
-rw-r--r--gn3/auth/authentication/oauth2/resource_server.py19
-rw-r--r--gn3/auth/authentication/oauth2/server.py72
-rw-r--r--gn3/auth/authentication/oauth2/views.py104
-rw-r--r--gn3/auth/authentication/users.py128
18 files changed, 0 insertions, 1019 deletions
diff --git a/gn3/auth/authentication/__init__.py b/gn3/auth/authentication/__init__.py
deleted file mode 100644
index 42ceacb..0000000
--- a/gn3/auth/authentication/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Handle authentication requests"""
-
-import bcrypt
-
-def credentials_in_database(cursor, email: str, password: str) -> bool:
-    """Check whether credentials are in the database."""
-    if len(email.strip()) == 0 or len(password.strip()) == 0:
-        return False
-
-    cursor.execute(
-        ("SELECT "
-         "users.email, user_credentials.password "
-         "FROM users LEFT JOIN user_credentials "
-         "ON users.user_id = user_credentials.user_id "
-         "WHERE users.email = :email"),
-        {"email": email})
-    results = cursor.fetchall()
-    if len(results) == 0:
-        return False
-
-    assert len(results) == 1, "Expected one row."
-    row = results[0]
-    return (email == row[0] and
-            bcrypt.checkpw(password.encode("utf-8"), row[1]))
diff --git a/gn3/auth/authentication/exceptions.py b/gn3/auth/authentication/exceptions.py
deleted file mode 100644
index c31e691..0000000
--- a/gn3/auth/authentication/exceptions.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""Exceptions for authentication"""
-
-class AuthenticationError(Exception):
-    """Base exception class for `gn3.auth.authentication` package."""
diff --git a/gn3/auth/authentication/oauth2/__init__.py b/gn3/auth/authentication/oauth2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/endpoints/__init__.py b/gn3/auth/authentication/oauth2/endpoints/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/endpoints/introspection.py b/gn3/auth/authentication/oauth2/endpoints/introspection.py
deleted file mode 100644
index cfe2998..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/introspection.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Handle introspection of tokens."""
-import datetime
-from urllib.parse import urlparse
-
-from flask import request as flask_request
-from authlib.oauth2.rfc7662 import (
-    IntrospectionEndpoint as _IntrospectionEndpoint)
-
-from gn3.auth.authentication.oauth2.models.oauth2token import OAuth2Token
-
-from .utilities import query_token as _query_token
-
-def get_token_user_sub(token: OAuth2Token) -> str:# pylint: disable=[unused-argument]
-    """
-    Return the token's subject as defined in
-    https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
-    """
-    ## For now a dummy return to prevent issues.
-    return "sub"
-
-class IntrospectionEndpoint(_IntrospectionEndpoint):
-    """Introspect token."""
-    def query_token(self, token_string: str, token_type_hint: str):
-        """Query the token."""
-        return _query_token(self, token_string, token_type_hint)
-
-    def introspect_token(self, token: OAuth2Token) -> dict:
-        """Return the introspection information."""
-        url = urlparse(flask_request.url)
-        return {
-            "active": True,
-            "scope": token.get_scope(),
-            "client_id": token.client.client_id,
-            "username": token.user.name,
-            "token_type": token.token_type,
-            "exp": int(token.expires_at.timestamp()),
-            "iat": int(token.issued_at.timestamp()),
-            "nbf": int(
-                (token.issued_at - datetime.timedelta(seconds=120)).timestamp()),
-            # "sub": get_token_user_sub(token),
-            "aud": token.client.client_id,
-            "iss": f"{url.scheme}://{url.netloc}",
-            "jti": token.token_id
-        }
-
-    def check_permission(self, token, client, request):
-        """Check that the client has permission to introspect token."""
-        return client.client_type == "internal"
diff --git a/gn3/auth/authentication/oauth2/endpoints/revocation.py b/gn3/auth/authentication/oauth2/endpoints/revocation.py
deleted file mode 100644
index b8517b6..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/revocation.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Handle token revocation."""
-
-from flask import current_app
-from authlib.oauth2.rfc7009 import RevocationEndpoint as _RevocationEndpoint
-
-from gn3.auth import db
-from gn3.auth.authentication.oauth2.models.oauth2token import (
-    save_token, OAuth2Token, revoke_token)
-
-from .utilities import query_token as _query_token
-
-class RevocationEndpoint(_RevocationEndpoint):
-    """Revoke the tokens"""
-    ENDPOINT_NAME = "revoke"
-    def query_token(self, token_string: str, token_type_hint: str):
-        """Query the token."""
-        return _query_token(self, token_string, token_type_hint)
-
-    def revoke_token(self, token: OAuth2Token, request):
-        """Revoke token `token`."""
-        with db.connection(current_app.config["AUTH_DB"]) as conn:
-            save_token(conn, revoke_token(token))
diff --git a/gn3/auth/authentication/oauth2/endpoints/utilities.py b/gn3/auth/authentication/oauth2/endpoints/utilities.py
deleted file mode 100644
index e13784e..0000000
--- a/gn3/auth/authentication/oauth2/endpoints/utilities.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""endpoint utilities"""
-from typing import Any, Optional
-
-from flask import current_app
-from pymonad.maybe import Nothing
-
-from gn3.auth import db
-from gn3.auth.authentication.oauth2.models.oauth2token import (
-    OAuth2Token, token_by_access_token, token_by_refresh_token)
-
-def query_token(# pylint: disable=[unused-argument]
-        endpoint_object: Any, token_str: str, token_type_hint) -> Optional[
-            OAuth2Token]:
-    """Retrieve the token from the database."""
-    def __identity__(val):
-        return val
-    token = Nothing
-    with db.connection(current_app.config["AUTH_DB"]) as conn:
-        if token_type_hint == "access_token":
-            token = token_by_access_token(conn, token_str)
-        if token_type_hint == "access_token":
-            token = token_by_refresh_token(conn, token_str)
-
-        return token.maybe(
-            token_by_access_token(conn, token_str).maybe(
-                token_by_refresh_token(conn, token_str).maybe(
-                    None, __identity__),
-                __identity__),
-            __identity__)
-
-    return None
diff --git a/gn3/auth/authentication/oauth2/grants/__init__.py b/gn3/auth/authentication/oauth2/grants/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/grants/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py b/gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py
deleted file mode 100644
index fb8d436..0000000
--- a/gn3/auth/authentication/oauth2/grants/authorisation_code_grant.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""Classes and function for Authorisation Code flow."""
-import uuid
-import string
-import random
-from typing import Optional
-from datetime import datetime
-
-from flask import current_app as app
-from authlib.oauth2.rfc6749 import grants
-from authlib.oauth2.rfc7636 import create_s256_code_challenge
-
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authentication.users import User
-
-from ..models.oauth2client import OAuth2Client
-from ..models.authorization_code import (
-    AuthorisationCode, authorisation_code, save_authorisation_code)
-
-class AuthorisationCodeGrant(grants.AuthorizationCodeGrant):
-    """Implement the 'Authorisation Code' grant."""
-    TOKEN_ENDPOINT_AUTH_METHODS: list[str] = [
-        "client_secret_basic", "client_secret_post"]
-    AUTHORIZATION_CODE_LENGTH: int = 48
-    TOKEN_ENDPOINT_HTTP_METHODS = ['POST']
-    GRANT_TYPE = "authorization_code"
-    RESPONSE_TYPES = {'code'}
-
-    def save_authorization_code(self, code, request):
-        """Persist the authorisation code to database."""
-        client = request.client
-        nonce = "".join(random.sample(string.ascii_letters + string.digits,
-                                      k=self.AUTHORIZATION_CODE_LENGTH))
-        return __save_authorization_code__(AuthorisationCode(
-            uuid.uuid4(), code, client, request.redirect_uri, request.scope,
-            nonce, int(datetime.now().timestamp()),
-            create_s256_code_challenge(app.config["SECRET_KEY"]),
-            "S256", request.user))
-
-    def query_authorization_code(self, code, client):
-        """Retrieve the code from the database."""
-        return __query_authorization_code__(code, client)
-
-    def delete_authorization_code(self, authorization_code):
-        """Delete the authorisation code."""
-        with db.connection(app.config["AUTH_DB"]) as conn:
-            with db.cursor(conn) as cursor:
-                cursor.execute(
-                    "DELETE FROM authorisation_code WHERE code_id=?",
-                    (str(authorization_code.code_id),))
-
-    def authenticate_user(self, authorization_code) -> Optional[User]:
-        """Authenticate the user who own the authorisation code."""
-        query = (
-            "SELECT users.* FROM authorisation_code LEFT JOIN users "
-            "ON authorisation_code.user_id=users.user_id "
-            "WHERE authorisation_code.code=?")
-        with db.connection(app.config["AUTH_DB"]) as conn:
-            with db.cursor(conn) as cursor:
-                cursor.execute(query, (str(authorization_code.code),))
-                res = cursor.fetchone()
-                if res:
-                    return User(
-                        uuid.UUID(res["user_id"]), res["email"], res["name"])
-
-        return None
-
-def __query_authorization_code__(
-        code: str, client: OAuth2Client) -> AuthorisationCode:
-    """A helper function that creates a new database connection.
-
-    This is found to be necessary since the `AuthorizationCodeGrant` class(es)
-    do not have a way to pass the database connection."""
-    def __auth_code__(conn) -> str:
-        the_code = authorisation_code(conn, code, client)
-        return the_code.maybe(None, lambda cde: cde) # type: ignore[misc, arg-type, return-value]
-
-    return with_db_connection(__auth_code__)
-
-def __save_authorization_code__(code: AuthorisationCode) -> AuthorisationCode:
-    """A helper function that creates a new database connection.
-
-    This is found to be necessary since the `AuthorizationCodeGrant` class(es)
-    do not have a way to pass the database connection."""
-    return with_db_connection(lambda conn: save_authorisation_code(conn, code))
diff --git a/gn3/auth/authentication/oauth2/grants/password_grant.py b/gn3/auth/authentication/oauth2/grants/password_grant.py
deleted file mode 100644
index 3233877..0000000
--- a/gn3/auth/authentication/oauth2/grants/password_grant.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""Allows users to authenticate directly."""
-
-from flask import current_app as app
-from authlib.oauth2.rfc6749 import grants
-
-from gn3.auth import db
-from gn3.auth.authentication.users import valid_login, user_by_email
-
-from gn3.auth.authorisation.errors import NotFoundError
-
-class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
-    """Implement the 'Password' grant."""
-    TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post"]
-
-    def authenticate_user(self, username, password):
-        "Authenticate the user with their username and password."
-        with db.connection(app.config["AUTH_DB"]) as conn:
-            try:
-                user = user_by_email(conn, username)
-                return user if valid_login(conn, user, password) else None
-            except NotFoundError as _nfe:
-                return None
diff --git a/gn3/auth/authentication/oauth2/models/__init__.py b/gn3/auth/authentication/oauth2/models/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/gn3/auth/authentication/oauth2/models/__init__.py
+++ /dev/null
diff --git a/gn3/auth/authentication/oauth2/models/authorization_code.py b/gn3/auth/authentication/oauth2/models/authorization_code.py
deleted file mode 100644
index f282814..0000000
--- a/gn3/auth/authentication/oauth2/models/authorization_code.py
+++ /dev/null
@@ -1,93 +0,0 @@
-"""Model and functions for handling the Authorisation Code"""
-from uuid import UUID
-from datetime import datetime
-from typing import NamedTuple
-
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-
-from .oauth2client import OAuth2Client
-
-from ...users import User, user_by_id
-
-__5_MINUTES__ = 300 # in seconds
-
-class AuthorisationCode(NamedTuple):
-    """
-    The AuthorisationCode model for the auth(entic|oris)ation system.
-    """
-    # Instance variables
-    code_id: UUID
-    code: str
-    client: OAuth2Client
-    redirect_uri: str
-    scope: str
-    nonce: str
-    auth_time: int
-    code_challenge: str
-    code_challenge_method: str
-    user: User
-
-    @property
-    def response_type(self) -> str:
-        """
-        For authorisation code flow, the response_type type MUST always be
-        'code'.
-        """
-        return "code"
-
-    def is_expired(self):
-        """Check whether the code is expired."""
-        return self.auth_time + __5_MINUTES__ < datetime.now().timestamp()
-
-    def get_redirect_uri(self):
-        """Get the redirect URI"""
-        return self.redirect_uri
-
-    def get_scope(self):
-        """Return the assigned scope for this AuthorisationCode."""
-        return self.scope
-
-    def get_nonce(self):
-        """Get the one-time use token."""
-        return self.nonce
-
-def authorisation_code(conn: db.DbConnection ,
-                       code: str,
-                       client: OAuth2Client) -> Maybe[AuthorisationCode]:
-    """
-    Retrieve the authorisation code object that corresponds to `code` and the
-    given OAuth2 client.
-    """
-    with db.cursor(conn) as cursor:
-        query = ("SELECT * FROM authorisation_code "
-                 "WHERE code=:code AND client_id=:client_id")
-        cursor.execute(
-            query, {"code": code, "client_id": str(client.client_id)})
-        result = cursor.fetchone()
-        if result:
-            return Just(AuthorisationCode(
-                UUID(result["code_id"]), result["code"], client,
-                result["redirect_uri"], result["scope"], result["nonce"],
-                int(result["auth_time"]), result["code_challenge"],
-                result["code_challenge_method"],
-                user_by_id(conn, UUID(result["user_id"]))))
-        return Nothing
-
-def save_authorisation_code(conn: db.DbConnection,
-                            auth_code: AuthorisationCode) -> AuthorisationCode:
-    """Persist the `auth_code` into the database."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "INSERT INTO authorisation_code VALUES("
-            ":code_id, :code, :client_id, :redirect_uri, :scope, :nonce, "
-            ":auth_time, :code_challenge, :code_challenge_method, :user_id"
-            ")",
-            {
-                **auth_code._asdict(),
-                "code_id": str(auth_code.code_id),
-                "client_id": str(auth_code.client.client_id),
-                "user_id": str(auth_code.user.user_id)
-            })
-        return auth_code
diff --git a/gn3/auth/authentication/oauth2/models/oauth2client.py b/gn3/auth/authentication/oauth2/models/oauth2client.py
deleted file mode 100644
index 2a307e3..0000000
--- a/gn3/auth/authentication/oauth2/models/oauth2client.py
+++ /dev/null
@@ -1,234 +0,0 @@
-"""OAuth2 Client model."""
-import json
-import datetime
-from uuid import UUID
-from typing import Sequence, Optional, NamedTuple
-
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-from gn3.auth.authentication.users import User, users, user_by_id, same_password
-
-from gn3.auth.authorisation.errors import NotFoundError
-
-class OAuth2Client(NamedTuple):
-    """
-    Client to the OAuth2 Server.
-
-    This is defined according to the mixin at
-    https://docs.authlib.org/en/latest/specs/rfc6749.html#authlib.oauth2.rfc6749.ClientMixin
-    """
-    client_id: UUID
-    client_secret: str
-    client_id_issued_at: datetime.datetime
-    client_secret_expires_at: datetime.datetime
-    client_metadata: dict
-    user: User
-
-    def check_client_secret(self, client_secret: str) -> bool:
-        """Check whether the `client_secret` matches this client."""
-        return same_password(client_secret, self.client_secret)
-
-    @property
-    def token_endpoint_auth_method(self) -> str:
-        """Return the token endpoint authorisation method."""
-        return self.client_metadata.get("token_endpoint_auth_method", ["none"])
-
-    @property
-    def client_type(self) -> str:
-        """
-        Return the token endpoint authorisation method.
-
-        Acceptable client types:
-        * public: Unable to use registered client secrets, e.g. browsers, apps
-          on mobile devices.
-        * confidential: able to securely authenticate with authorisation server
-          e.g. being able to keep their registered client secret safe.
-        """
-        return self.client_metadata.get("client_type", "public")
-
-    def check_endpoint_auth_method(self, method: str, endpoint: str) -> bool:
-        """
-        Check if the client supports the given method for the given endpoint.
-
-        Acceptable methods:
-        * none: Client is a public client and does not have a client secret
-        * client_secret_post: Client uses the HTTP POST parameters
-        * client_secret_basic: Client uses HTTP Basic
-        """
-        if endpoint == "token":
-            return (method in self.token_endpoint_auth_method
-                    and method == "client_secret_post")
-        if endpoint in ("introspection", "revoke"):
-            return (method in self.token_endpoint_auth_method
-                    and method == "client_secret_basic")
-        return False
-
-    @property
-    def id(self):# pylint: disable=[invalid-name]
-        """Return the client_id."""
-        return self.client_id
-
-    @property
-    def grant_types(self) -> Sequence[str]:
-        """
-        Return the grant types that this client supports.
-
-        Valid grant types:
-        * authorisation_code
-        * implicit
-        * client_credentials
-        * password
-        """
-        return self.client_metadata.get("grant_types", [])
-
-    def check_grant_type(self, grant_type: str) -> bool:
-        """
-        Validate that client can handle the given grant types
-        """
-        return grant_type in self.grant_types
-
-    @property
-    def redirect_uris(self) -> Sequence[str]:
-        """Return the redirect_uris that this client supports."""
-        return self.client_metadata.get('redirect_uris', [])
-
-    def check_redirect_uri(self, redirect_uri: str) -> bool:
-        """
-        Check whether the given `redirect_uri` is one of the expected ones.
-        """
-        return redirect_uri in self.redirect_uris
-
-    @property
-    def response_types(self) -> Sequence[str]:
-        """Return the response_types that this client supports."""
-        return self.client_metadata.get("response_type", [])
-
-    def check_response_type(self, response_type: str) -> bool:
-        """Check whether this client supports `response_type`."""
-        return response_type in self.response_types
-
-    @property
-    def scope(self) -> Sequence[str]:
-        """Return valid scopes for this client."""
-        return tuple(set(self.client_metadata.get("scope", [])))
-
-    def get_allowed_scope(self, scope: str) -> str:
-        """Return list of scopes in `scope` that are supported by this client."""
-        if not bool(scope):
-            return ""
-        requested = scope.split()
-        return " ".join(sorted(set(
-            scp for scp in requested if scp in self.scope)))
-
-    def get_client_id(self):
-        """Return this client's identifier."""
-        return self.client_id
-
-    def get_default_redirect_uri(self) -> str:
-        """Return the default redirect uri"""
-        return self.client_metadata.get("default_redirect_uri", "")
-
-def client(conn: db.DbConnection, client_id: UUID,
-           user: Optional[User] = None) -> Maybe:
-    """Retrieve a client by its ID"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM oauth2_clients WHERE client_id=?", (str(client_id),))
-        result = cursor.fetchone()
-        the_user = user
-        if result:
-            if not bool(the_user):
-                try:
-                    the_user = user_by_id(conn, result["user_id"])
-                except NotFoundError as _nfe:
-                    the_user = None
-
-            return Just(
-                OAuth2Client(UUID(result["client_id"]),
-                             result["client_secret"],
-                             datetime.datetime.fromtimestamp(
-                                 result["client_id_issued_at"]),
-                             datetime.datetime.fromtimestamp(
-                                 result["client_secret_expires_at"]),
-                             json.loads(result["client_metadata"]),
-                             the_user))# type: ignore[arg-type]
-
-    return Nothing
-
-def client_by_id_and_secret(conn: db.DbConnection, client_id: UUID,
-                            client_secret: str) -> OAuth2Client:
-    """Retrieve a client by its ID and secret"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM oauth2_clients WHERE client_id=?",
-            (str(client_id),))
-        row = cursor.fetchone()
-        if bool(row) and same_password(client_secret, row["client_secret"]):
-            return OAuth2Client(
-                client_id, client_secret,
-                datetime.datetime.fromtimestamp(row["client_id_issued_at"]),
-                datetime.datetime.fromtimestamp(
-                    row["client_secret_expires_at"]),
-                json.loads(row["client_metadata"]),
-                user_by_id(conn, UUID(row["user_id"])))
-
-        raise NotFoundError("Could not find client with the given credentials.")
-
-def save_client(conn: db.DbConnection, the_client: OAuth2Client) -> OAuth2Client:
-    """Persist the client details into the database."""
-    with db.cursor(conn) as cursor:
-        query = (
-            "INSERT INTO oauth2_clients "
-            "(client_id, client_secret, client_id_issued_at, "
-            "client_secret_expires_at, client_metadata, user_id) "
-            "VALUES "
-            "(:client_id, :client_secret, :client_id_issued_at, "
-            ":client_secret_expires_at, :client_metadata, :user_id) "
-            "ON CONFLICT (client_id) DO UPDATE SET "
-            "client_secret=:client_secret, "
-            "client_id_issued_at=:client_id_issued_at, "
-            "client_secret_expires_at=:client_secret_expires_at, "
-            "client_metadata=:client_metadata, user_id=:user_id")
-        cursor.execute(
-            query,
-            {
-                "client_id": str(the_client.client_id),
-                "client_secret": the_client.client_secret,
-                "client_id_issued_at": (
-                    the_client.client_id_issued_at.timestamp()),
-                "client_secret_expires_at": (
-                    the_client.client_secret_expires_at.timestamp()),
-                "client_metadata": json.dumps(the_client.client_metadata),
-                "user_id": str(the_client.user.user_id)
-            })
-        return the_client
-
-def oauth2_clients(conn: db.DbConnection) -> tuple[OAuth2Client, ...]:
-    """Fetch a list of all OAuth2 clients."""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM oauth2_clients")
-        clients_rs = cursor.fetchall()
-        the_users = {
-            usr.user_id: usr for usr in users(
-                conn, tuple({UUID(result["user_id"]) for result in clients_rs}))
-        }
-        return tuple(OAuth2Client(UUID(result["client_id"]),
-                                  result["client_secret"],
-                                  datetime.datetime.fromtimestamp(
-                                     result["client_id_issued_at"]),
-                                  datetime.datetime.fromtimestamp(
-                                     result["client_secret_expires_at"]),
-                                  json.loads(result["client_metadata"]),
-                                  the_users[UUID(result["user_id"])])
-                     for result in clients_rs)
-
-def delete_client(conn: db.DbConnection, the_client: OAuth2Client) -> OAuth2Client:
-    """Delete the given client from the database"""
-    with db.cursor(conn) as cursor:
-        params = (str(the_client.client_id),)
-        cursor.execute("DELETE FROM authorisation_code WHERE client_id=?",
-                       params)
-        cursor.execute("DELETE FROM oauth2_tokens WHERE client_id=?", params)
-        cursor.execute("DELETE FROM oauth2_clients WHERE client_id=?", params)
-        return the_client
diff --git a/gn3/auth/authentication/oauth2/models/oauth2token.py b/gn3/auth/authentication/oauth2/models/oauth2token.py
deleted file mode 100644
index bfe4aaf..0000000
--- a/gn3/auth/authentication/oauth2/models/oauth2token.py
+++ /dev/null
@@ -1,133 +0,0 @@
-"""OAuth2 Token"""
-import uuid
-import datetime
-from typing import NamedTuple, Optional
-
-from pymonad.maybe import Just, Maybe, Nothing
-
-from gn3.auth import db
-from gn3.auth.authentication.users import User, user_by_id
-
-from gn3.auth.authorisation.errors import NotFoundError
-
-from .oauth2client import client, OAuth2Client
-
-class OAuth2Token(NamedTuple):
-    """Implement Tokens for OAuth2."""
-    token_id: uuid.UUID
-    client: OAuth2Client
-    token_type: str
-    access_token: str
-    refresh_token: Optional[str]
-    scope: str
-    revoked: bool
-    issued_at: datetime.datetime
-    expires_in: int
-    user: User
-
-    @property
-    def expires_at(self) -> datetime.datetime:
-        """Return the time when the token expires."""
-        return self.issued_at + datetime.timedelta(seconds=self.expires_in)
-
-    def check_client(self, client: OAuth2Client) -> bool:# pylint: disable=[redefined-outer-name]
-        """Check whether the token is issued to given `client`."""
-        return client.client_id == self.client.client_id
-
-    def get_expires_in(self) -> int:
-        """Return the `expires_in` value for the token."""
-        return self.expires_in
-
-    def get_scope(self) -> str:
-        """Return the valid scope for the token."""
-        return self.scope
-
-    def is_expired(self) -> bool:
-        """Check whether the token is expired."""
-        return self.expires_at < datetime.datetime.now()
-
-    def is_revoked(self):
-        """Check whether the token has been revoked."""
-        return self.revoked
-
-def __token_from_resultset__(conn: db.DbConnection, rset) -> Maybe:
-    def __identity__(val):
-        return val
-    try:
-        the_user = user_by_id(conn, uuid.UUID(rset["user_id"]))
-    except NotFoundError as _nfe:
-        the_user = None
-    the_client = client(conn, uuid.UUID(rset["client_id"]), the_user)
-
-    if the_client.is_just() and bool(the_user):
-        return Just(OAuth2Token(token_id=uuid.UUID(rset["token_id"]),
-                                client=the_client.maybe(None, __identity__),
-                                token_type=rset["token_type"],
-                                access_token=rset["access_token"],
-                                refresh_token=rset["refresh_token"],
-                                scope=rset["scope"],
-                                revoked=(rset["revoked"] == 1),
-                                issued_at=datetime.datetime.fromtimestamp(
-                                    rset["issued_at"]),
-                                expires_in=rset["expires_in"],
-                                user=the_user))# type: ignore[arg-type]
-
-    return Nothing
-
-def token_by_access_token(conn: db.DbConnection, token_str: str) -> Maybe:
-    """Retrieve token by its token string"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM oauth2_tokens WHERE access_token=?",
-                       (token_str,))
-        res = cursor.fetchone()
-        if res:
-            return __token_from_resultset__(conn, res)
-
-    return Nothing
-
-def token_by_refresh_token(conn: db.DbConnection, token_str: str) -> Maybe:
-    """Retrieve token by its token string"""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT * FROM oauth2_tokens WHERE refresh_token=?",
-            (token_str,))
-        res = cursor.fetchone()
-        if res:
-            return __token_from_resultset__(conn, res)
-
-    return Nothing
-
-def revoke_token(token: OAuth2Token) -> OAuth2Token:
-    """
-    Return a new token derived from `token` with the `revoked` field set to
-    `True`.
-    """
-    return OAuth2Token(
-        token_id=token.token_id, client=token.client,
-        token_type=token.token_type, access_token=token.access_token,
-        refresh_token=token.refresh_token, scope=token.scope, revoked=True,
-        issued_at=token.issued_at, expires_in=token.expires_in, user=token.user)
-
-def save_token(conn: db.DbConnection, token: OAuth2Token) -> None:
-    """Save/Update the token."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("INSERT INTO oauth2_tokens VALUES (:token_id, :client_id, "
-             ":token_type, :access_token, :refresh_token, :scope, :revoked, "
-             ":issued_at, :expires_in, :user_id) "
-             "ON CONFLICT (token_id) DO UPDATE SET "
-             "refresh_token=:refresh_token, revoked=:revoked, "
-             "expires_in=:expires_in "
-             "WHERE token_id=:token_id"),
-            {
-                "token_id": str(token.token_id),
-                "client_id": str(token.client.client_id),
-                "token_type": token.token_type,
-                "access_token": token.access_token,
-                "refresh_token": token.refresh_token,
-                "scope": token.scope,
-                "revoked": 1 if token.revoked else 0,
-                "issued_at": int(token.issued_at.timestamp()),
-                "expires_in": token.expires_in,
-                "user_id": str(token.user.user_id)
-            })
diff --git a/gn3/auth/authentication/oauth2/resource_server.py b/gn3/auth/authentication/oauth2/resource_server.py
deleted file mode 100644
index 223e811..0000000
--- a/gn3/auth/authentication/oauth2/resource_server.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Protect the resources endpoints"""
-
-from flask import current_app as app
-from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator
-from authlib.integrations.flask_oauth2 import ResourceProtector
-
-from gn3.auth import db
-from gn3.auth.authentication.oauth2.models.oauth2token import token_by_access_token
-
-class BearerTokenValidator(_BearerTokenValidator):
-    """Extends `authlib.oauth2.rfc6750.BearerTokenValidator`"""
-    def authenticate_token(self, token_string: str):
-        with db.connection(app.config["AUTH_DB"]) as conn:
-            return token_by_access_token(conn, token_string).maybe(# type: ignore[misc]
-                None, lambda tok: tok)
-
-require_oauth = ResourceProtector()
-
-require_oauth.register_token_validator(BearerTokenValidator())
diff --git a/gn3/auth/authentication/oauth2/server.py b/gn3/auth/authentication/oauth2/server.py
deleted file mode 100644
index 7d7113a..0000000
--- a/gn3/auth/authentication/oauth2/server.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""Initialise the OAuth2 Server"""
-import uuid
-import datetime
-from typing import Callable
-
-from flask import Flask, current_app
-from authlib.oauth2.rfc6749.errors import InvalidClientError
-from authlib.integrations.flask_oauth2 import AuthorizationServer
-# from authlib.oauth2.rfc7636 import CodeChallenge
-
-from gn3.auth import db
-
-from .models.oauth2client import client
-from .models.oauth2token import OAuth2Token, save_token
-
-from .grants.password_grant import PasswordGrant
-from .grants.authorisation_code_grant import AuthorisationCodeGrant
-
-from .endpoints.revocation import RevocationEndpoint
-from .endpoints.introspection import IntrospectionEndpoint
-
-def create_query_client_func() -> Callable:
-    """Create the function that loads the client."""
-    def __query_client__(client_id: uuid.UUID):
-        # use current_app rather than passing the db_uri to avoid issues
-        # when config changes, e.g. while testing.
-        with db.connection(current_app.config["AUTH_DB"]) as conn:
-            the_client = client(conn, client_id).maybe(
-                None, lambda clt: clt) # type: ignore[misc]
-            if bool(the_client):
-                return the_client
-            raise InvalidClientError(
-                "No client found for the given CLIENT_ID and CLIENT_SECRET.")
-
-    return __query_client__
-
-def create_save_token_func(token_model: type) -> Callable:
-    """Create the function that saves the token."""
-    def __save_token__(token, request):
-        with db.connection(current_app.config["AUTH_DB"]) as conn:
-            save_token(
-                conn, token_model(
-                    token_id=uuid.uuid4(), client=request.client,
-                    user=request.user,
-                    **{
-                        "refresh_token": None, "revoked": False,
-                        "issued_at": datetime.datetime.now(),
-                        **token
-                    }))
-
-    return __save_token__
-
-def setup_oauth2_server(app: Flask) -> None:
-    """Set's up the oauth2 server for the flask application."""
-    server = AuthorizationServer()
-    server.register_grant(PasswordGrant)
-
-    # Figure out a common `code_verifier` for GN2 and GN3 and set
-    # server.register_grant(AuthorisationCodeGrant, [CodeChallenge(required=False)])
-    # below
-    server.register_grant(AuthorisationCodeGrant)
-
-    # register endpoints
-    server.register_endpoint(RevocationEndpoint)
-    server.register_endpoint(IntrospectionEndpoint)
-
-    # init server
-    server.init_app(
-        app,
-        query_client=create_query_client_func(),
-        save_token=create_save_token_func(OAuth2Token))
-    app.config["OAUTH2_SERVER"] = server
diff --git a/gn3/auth/authentication/oauth2/views.py b/gn3/auth/authentication/oauth2/views.py
deleted file mode 100644
index 2bd3865..0000000
--- a/gn3/auth/authentication/oauth2/views.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""Endpoints for the oauth2 server"""
-import uuid
-import traceback
-
-from authlib.oauth2.rfc6749.errors import InvalidClientError
-from email_validator import validate_email, EmailNotValidError
-from flask import (
-    flash,
-    request,
-    url_for,
-    redirect,
-    Response,
-    Blueprint,
-    render_template,
-    current_app as app)
-
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.errors import ForbiddenAccess
-
-from .resource_server import require_oauth
-from .endpoints.revocation import RevocationEndpoint
-from .endpoints.introspection import IntrospectionEndpoint
-
-from ..users import valid_login, NotFoundError, user_by_email
-
-auth = Blueprint("auth", __name__)
-
-@auth.route("/delete-client/<uuid:client_id>", methods=["GET", "POST"])
-def delete_client(client_id: uuid.UUID):
-    """Delete an OAuth2 client."""
-    return f"WOULD DELETE OAUTH2 CLIENT {client_id}."
-
-@auth.route("/authorise", methods=["GET", "POST"])
-def authorise():
-    """Authorise a user"""
-    try:
-        server = app.config["OAUTH2_SERVER"]
-        client_id = uuid.UUID(request.args.get(
-            "client_id",
-            request.form.get("client_id", str(uuid.uuid4()))))
-        client = server.query_client(client_id)
-        if not bool(client):
-            flash("Invalid OAuth2 client.", "alert-error")
-        if request.method == "GET":
-            client = server.query_client(request.args.get("client_id"))
-            return render_template(
-                "oauth2/authorise-user.html",
-                client=client,
-                scope=client.scope,
-                response_type="code")
-
-        form = request.form
-        def __authorise__(conn: db.DbConnection) -> Response:
-            email_passwd_msg = "Email or password is invalid!"
-            redirect_response = redirect(url_for("oauth2.auth.authorise",
-                                                 client_id=client_id))
-            try:
-                email = validate_email(
-                    form.get("user:email"), check_deliverability=False)
-                user = user_by_email(conn, email["email"])
-                if valid_login(conn, user, form.get("user:password", "")):
-                    return server.create_authorization_response(request=request, grant_user=user)
-                flash(email_passwd_msg, "alert-error")
-                return redirect_response # type: ignore[return-value]
-            except EmailNotValidError as _enve:
-                app.logger.debug(traceback.format_exc())
-                flash(email_passwd_msg, "alert-error")
-                return redirect_response # type: ignore[return-value]
-            except NotFoundError as _nfe:
-                app.logger.debug(traceback.format_exc())
-                flash(email_passwd_msg, "alert-error")
-                return redirect_response # type: ignore[return-value]
-
-        return with_db_connection(__authorise__)
-    except InvalidClientError as ice:
-        return render_template(
-            "oauth2/oauth2_error.html", error=ice), ice.status_code
-
-@auth.route("/token", methods=["POST"])
-def token():
-    """Retrieve the authorisation token."""
-    server = app.config["OAUTH2_SERVER"]
-    return server.create_token_response()
-
-@auth.route("/revoke", methods=["POST"])
-def revoke_token():
-    """Revoke the token."""
-    return app.config["OAUTH2_SERVER"].create_endpoint_response(
-        RevocationEndpoint.ENDPOINT_NAME)
-
-@auth.route("/introspect", methods=["POST"])
-@require_oauth("introspect")
-def introspect_token() -> Response:
-    """Provide introspection information for the token."""
-    # This is dangerous to provide publicly
-    authorised_clients = app.config.get(
-        "OAUTH2_CLIENTS_WITH_INTROSPECTION_PRIVILEGE", [])
-    with require_oauth.acquire("introspect") as the_token:
-        if the_token.client.client_id in authorised_clients:
-            return app.config["OAUTH2_SERVER"].create_endpoint_response(
-                IntrospectionEndpoint.ENDPOINT_NAME)
-
-    raise ForbiddenAccess("You cannot access this endpoint")
diff --git a/gn3/auth/authentication/users.py b/gn3/auth/authentication/users.py
deleted file mode 100644
index 0e72ed2..0000000
--- a/gn3/auth/authentication/users.py
+++ /dev/null
@@ -1,128 +0,0 @@
-"""User-specific code and data structures."""
-from uuid import UUID, uuid4
-from typing import Any, Tuple, NamedTuple
-
-from argon2 import PasswordHasher
-from argon2.exceptions import VerifyMismatchError
-
-from gn3.auth import db
-from gn3.auth.authorisation.errors import NotFoundError
-
-class User(NamedTuple):
-    """Class representing a user."""
-    user_id: UUID
-    email: str
-    name: str
-
-    def get_user_id(self):
-        """Return the user's UUID. Mostly for use with Authlib."""
-        return self.user_id
-
-    def dictify(self) -> dict[str, Any]:
-        """Return a dict representation of `User` objects."""
-        return {"user_id": self.user_id, "email": self.email, "name": self.name}
-
-DUMMY_USER = User(user_id=UUID("a391cf60-e8b7-4294-bd22-ddbbda4b3530"),
-                  email="gn3@dummy.user",
-                  name="Dummy user to use as placeholder")
-
-def user_by_email(conn: db.DbConnection, email: str) -> User:
-    """Retrieve user from database by their email address"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM users WHERE email=?", (email,))
-        row = cursor.fetchone()
-
-    if row:
-        return User(UUID(row["user_id"]), row["email"], row["name"])
-
-    raise NotFoundError(f"Could not find user with email {email}")
-
-def user_by_id(conn: db.DbConnection, user_id: UUID) -> User:
-    """Retrieve user from database by their user id"""
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM users WHERE user_id=?", (str(user_id),))
-        row = cursor.fetchone()
-
-    if row:
-        return User(UUID(row["user_id"]), row["email"], row["name"])
-
-    raise NotFoundError(f"Could not find user with ID {user_id}")
-
-def same_password(password: str, hashed: str) -> bool:
-    """Check that `raw_password` is hashed to `hash`"""
-    try:
-        return hasher().verify(hashed, password)
-    except VerifyMismatchError as _vme:
-        return False
-
-def valid_login(conn: db.DbConnection, user: User, password: str) -> bool:
-    """Check the validity of the provided credentials for login."""
-    with db.cursor(conn) as cursor:
-        cursor.execute(
-            ("SELECT * FROM users LEFT JOIN user_credentials "
-             "ON users.user_id=user_credentials.user_id "
-             "WHERE users.user_id=?"),
-            (str(user.user_id),))
-        row = cursor.fetchone()
-
-    if row is None:
-        return False
-
-    return same_password(password, 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 hasher():
-    """Retrieve PasswordHasher object"""
-    # TODO: Maybe tune the parameters here...
-    # Tuneable Parameters:
-    # - time_cost (default: 2)
-    # - memory_cost (default: 102400)
-    # - parallelism (default: 8)
-    # - hash_len (default: 16)
-    # - salt_len (default: 16)
-    # - encoding (default: 'utf-8')
-    # - type (default: <Type.ID: 2>)
-    return PasswordHasher()
-
-def hash_password(password):
-    """Hash the password."""
-    return hasher().hash(password)
-
-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 = hash_password(password)
-    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
-
-def users(conn: db.DbConnection,
-          ids: tuple[UUID, ...] = tuple()) -> tuple[User, ...]:
-    """
-    Fetch all users with the given `ids`. If `ids` is empty, return ALL users.
-    """
-    params = ", ".join(["?"] * len(ids))
-    with db.cursor(conn) as cursor:
-        query = "SELECT * FROM users" + (
-            f" WHERE user_id IN ({params})"
-            if len(ids) > 0 else "")
-        print(query)
-        cursor.execute(query, tuple(str(the_id) for the_id in ids))
-        return tuple(User(UUID(row["user_id"]), row["email"], row["name"])
-                     for row in cursor.fetchall())
-    return tuple()