about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn3/app.py2
-rw-r--r--gn3/auth/__init__.py1
-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/server.py72
-rw-r--r--gn3/auth/authentication/oauth2/views.py104
-rw-r--r--gn3/auth/authorisation/checks.py3
-rw-r--r--gn3/auth/authorisation/data/views.py4
-rw-r--r--gn3/auth/authorisation/groups/models.py2
-rw-r--r--gn3/auth/authorisation/groups/views.py5
-rw-r--r--gn3/auth/authorisation/oauth2/oauth2client.py (renamed from gn3/auth/authentication/oauth2/models/oauth2client.py)2
-rw-r--r--gn3/auth/authorisation/oauth2/oauth2token.py (renamed from gn3/auth/authentication/oauth2/models/oauth2token.py)2
-rw-r--r--gn3/auth/authorisation/oauth2/resource_server.py (renamed from gn3/auth/authentication/oauth2/resource_server.py)2
-rw-r--r--gn3/auth/authorisation/privileges.py2
-rw-r--r--gn3/auth/authorisation/resources/checks.py2
-rw-r--r--gn3/auth/authorisation/resources/models.py2
-rw-r--r--gn3/auth/authorisation/resources/views.py4
-rw-r--r--gn3/auth/authorisation/roles/models.py2
-rw-r--r--gn3/auth/authorisation/roles/views.py3
-rw-r--r--gn3/auth/authorisation/users/__init__.py12
-rw-r--r--gn3/auth/authorisation/users/admin/__init__.py2
-rw-r--r--gn3/auth/authorisation/users/admin/ui.py27
-rw-r--r--gn3/auth/authorisation/users/admin/views.py230
-rw-r--r--gn3/auth/authorisation/users/base.py (renamed from gn3/auth/authentication/users.py)0
-rw-r--r--gn3/auth/authorisation/users/collections/views.py4
-rw-r--r--gn3/auth/authorisation/users/masquerade/__init__.py1
-rw-r--r--gn3/auth/authorisation/users/masquerade/models.py67
-rw-r--r--gn3/auth/authorisation/users/masquerade/views.py48
-rw-r--r--gn3/auth/authorisation/users/models.py2
-rw-r--r--gn3/auth/authorisation/users/views.py9
-rw-r--r--gn3/auth/views.py5
-rw-r--r--main.py16
-rw-r--r--migrations/auth/20221103_01_js9ub-initialise-the-auth-entic-oris-ation-database.py19
-rw-r--r--migrations/auth/20221103_02_sGrIs-create-user-credentials-table.py20
-rw-r--r--migrations/auth/20221108_01_CoxYh-create-the-groups-table.py19
-rw-r--r--migrations/auth/20221108_02_wxTr9-create-privileges-table.py18
-rw-r--r--migrations/auth/20221108_03_Pbhb1-create-resource-categories-table.py19
-rw-r--r--migrations/auth/20221108_04_CKcSL-init-data-in-resource-categories-table.py25
-rw-r--r--migrations/auth/20221109_01_HbD5F-add-resource-meta-field-to-resource-categories-field.py17
-rw-r--r--migrations/auth/20221110_01_WtZ1I-create-resources-table.py26
-rw-r--r--migrations/auth/20221110_05_BaNtL-create-roles-table.py19
-rw-r--r--migrations/auth/20221110_06_Pq2kT-create-generic-roles-table.py24
-rw-r--r--migrations/auth/20221110_07_7WGa1-create-role-privileges-table.py29
-rw-r--r--migrations/auth/20221110_08_23psB-add-privilege-category-and-privilege-description-columns-to-privileges-table.py22
-rw-r--r--migrations/auth/20221113_01_7M0hv-enumerate-initial-privileges.py66
-rw-r--r--migrations/auth/20221114_01_n8gsF-create-generic-role-privileges-table.py35
-rw-r--r--migrations/auth/20221114_02_DKKjn-drop-generic-role-tables.py41
-rw-r--r--migrations/auth/20221114_03_PtWjc-create-group-roles-table.py29
-rw-r--r--migrations/auth/20221114_04_tLUzB-initialise-basic-roles.py56
-rw-r--r--migrations/auth/20221114_05_hQun6-create-user-roles-table.py29
-rw-r--r--migrations/auth/20221116_01_nKUmX-add-privileges-to-group-leader-role.py35
-rw-r--r--migrations/auth/20221117_01_RDlfx-modify-group-roles-add-group-role-id.py52
-rw-r--r--migrations/auth/20221117_02_fmuZh-create-group-users-table.py25
-rw-r--r--migrations/auth/20221206_01_BbeF9-create-group-user-roles-on-resources-table.py39
-rw-r--r--migrations/auth/20221208_01_sSdHz-add-public-column-to-resources-table.py16
-rw-r--r--migrations/auth/20221219_01_CI3tN-create-oauth2-clients-table.py25
-rw-r--r--migrations/auth/20221219_02_buSEU-create-oauth2-tokens-table.py31
-rw-r--r--migrations/auth/20221219_03_PcTrb-create-authorisation-code-table.py31
-rw-r--r--migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py40
-rw-r--r--migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py111
-rw-r--r--migrations/auth/20230207_01_r0bkZ-create-group-join-requests-table.py29
-rw-r--r--migrations/auth/20230210_01_8xMa1-system-admin-privileges-for-data-distribution.py22
-rw-r--r--migrations/auth/20230210_02_lDK14-create-system-admin-role.py38
-rw-r--r--migrations/auth/20230306_01_pRfxl-add-system-user-list-privilege.py26
-rw-r--r--migrations/auth/20230306_02_7GnRY-add-system-user-list-privilege-to-system-administrator-and-group-leader-roles.py42
-rw-r--r--migrations/auth/20230322_01_0dDZR-create-linked-phenotype-data-table.py30
-rw-r--r--migrations/auth/20230322_02_Ll854-create-phenotype-resources-table.py29
-rw-r--r--migrations/auth/20230404_01_VKxXg-create-linked-genotype-data-table.py29
-rw-r--r--migrations/auth/20230404_02_la33P-create-genotype-resources-table.py29
-rw-r--r--migrations/auth/20230410_01_8mwaf-create-linked-mrna-data-table.py30
-rw-r--r--migrations/auth/20230410_02_WZqSf-create-mrna-resources-table.py28
-rw-r--r--scripts/migrate_existing_data.py381
-rw-r--r--scripts/register_sys_admin.py81
-rw-r--r--tests/unit/auth/__init__.py0
-rw-r--r--tests/unit/auth/conftest.py24
-rw-r--r--tests/unit/auth/fixtures/__init__.py8
-rw-r--r--tests/unit/auth/fixtures/group_fixtures.py147
-rw-r--r--tests/unit/auth/fixtures/migration_fixtures.py51
-rw-r--r--tests/unit/auth/fixtures/oauth2_client_fixtures.py51
-rw-r--r--tests/unit/auth/fixtures/resource_fixtures.py25
-rw-r--r--tests/unit/auth/fixtures/role_fixtures.py45
-rw-r--r--tests/unit/auth/fixtures/user_fixtures.py66
-rw-r--r--tests/unit/auth/test_credentials.py100
-rw-r--r--tests/unit/auth/test_groups.py171
-rw-r--r--tests/unit/auth/test_migrations_add_data_to_table.py79
-rw-r--r--tests/unit/auth/test_migrations_add_remove_columns.py116
-rw-r--r--tests/unit/auth/test_migrations_create_tables.py91
-rw-r--r--tests/unit/auth/test_migrations_drop_tables.py63
-rw-r--r--tests/unit/auth/test_migrations_indexes.py97
-rw-r--r--tests/unit/auth/test_migrations_init_data_in_resource_categories_table.py60
-rw-r--r--tests/unit/auth/test_migrations_insert_data_into_empty_table.py77
-rw-r--r--tests/unit/auth/test_privileges.py45
-rw-r--r--tests/unit/auth/test_resources.py124
-rw-r--r--tests/unit/auth/test_roles.py127
-rw-r--r--tests/unit/auth/test_token.py62
105 files changed, 35 insertions, 4272 deletions
diff --git a/gn3/app.py b/gn3/app.py
index da87f2b..2a11131 100644
--- a/gn3/app.py
+++ b/gn3/app.py
@@ -27,7 +27,6 @@ from gn3.api.search import search
 from gn3.api.metadata import metadata
 from gn3.api.sampledata import sampledata
 from gn3.auth import oauth2
-from gn3.auth.authentication.oauth2.server import setup_oauth2_server
 
 
 def create_app(config: Union[Dict, str, None] = None) -> Flask:
@@ -79,5 +78,4 @@ def create_app(config: Union[Dict, str, None] = None) -> Flask:
     app.register_blueprint(oauth2, url_prefix="/api/oauth2")
 
     register_error_handlers(app)
-    setup_oauth2_server(app)
     return app
diff --git a/gn3/auth/__init__.py b/gn3/auth/__init__.py
index a28498d..cd65e9b 100644
--- a/gn3/auth/__init__.py
+++ b/gn3/auth/__init__.py
@@ -1,5 +1,4 @@
 """Top-Level `Auth` module"""
 from . import authorisation
-from . import authentication
 
 from .views import oauth2
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/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/authorisation/checks.py b/gn3/auth/authorisation/checks.py
index 1c87c02..17daca4 100644
--- a/gn3/auth/authorisation/checks.py
+++ b/gn3/auth/authorisation/checks.py
@@ -5,12 +5,11 @@ from typing import Callable
 from flask import request, current_app as app
 
 from gn3.auth import db
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
 
 from . import privileges as auth_privs
 from .errors import InvalidData, AuthorisationError
 
-from ..authentication.oauth2.resource_server import require_oauth
-
 def __system_privileges_in_roles__(conn, user):
     """
     This really is a hack since groups are not treated as resources at the
diff --git a/gn3/auth/authorisation/data/views.py b/gn3/auth/authorisation/data/views.py
index 8adf862..81811dd 100644
--- a/gn3/auth/authorisation/data/views.py
+++ b/gn3/auth/authorisation/data/views.py
@@ -29,9 +29,9 @@ from gn3.auth.authorisation.resources.checks import authorised_for
 from gn3.auth.authorisation.resources.models import (
     user_resources, public_resources, attach_resources_data)
 
-from gn3.auth.authentication.users import User
-from gn3.auth.authentication.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
 
+from gn3.auth.authorisation.users import User
 from gn3.auth.authorisation.data.phenotypes import link_phenotype_data
 from gn3.auth.authorisation.data.mrna import link_mrna_data, ungrouped_mrna_data
 from gn3.auth.authorisation.data.genotypes import (
diff --git a/gn3/auth/authorisation/groups/models.py b/gn3/auth/authorisation/groups/models.py
index 5a3ae50..7212a78 100644
--- a/gn3/auth/authorisation/groups/models.py
+++ b/gn3/auth/authorisation/groups/models.py
@@ -9,7 +9,7 @@ from pymonad.maybe import Just, Maybe, Nothing
 
 from gn3.auth import db
 from gn3.auth.dictify import dictify
-from gn3.auth.authentication.users import User, user_by_id
+from gn3.auth.authorisation.users import User, user_by_id
 
 from ..checks import authorised_p
 from ..privileges import Privilege
diff --git a/gn3/auth/authorisation/groups/views.py b/gn3/auth/authorisation/groups/views.py
index 628df36..a849a73 100644
--- a/gn3/auth/authorisation/groups/views.py
+++ b/gn3/auth/authorisation/groups/views.py
@@ -12,6 +12,8 @@ from gn3 import db_utils as gn3db
 
 from gn3.auth.dictify import dictify
 from gn3.auth.db_utils import with_db_connection
+from gn3.auth.authorisation.users import User
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
 
 from .data import link_data_to_group
 from .models import (
@@ -28,9 +30,6 @@ from ..checks import authorised_p
 from ..privileges import Privilege, privileges_by_ids
 from ..errors import InvalidData, NotFoundError, AuthorisationError
 
-from ...authentication.users import User
-from ...authentication.oauth2.resource_server import require_oauth
-
 groups = Blueprint("groups", __name__)
 
 @groups.route("/list", methods=["GET"])
diff --git a/gn3/auth/authentication/oauth2/models/oauth2client.py b/gn3/auth/authorisation/oauth2/oauth2client.py
index 2a307e3..dc54a41 100644
--- a/gn3/auth/authentication/oauth2/models/oauth2client.py
+++ b/gn3/auth/authorisation/oauth2/oauth2client.py
@@ -7,9 +7,9 @@ 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
+from gn3.auth.authorisation.users import User, users, user_by_id, same_password
 
 class OAuth2Client(NamedTuple):
     """
diff --git a/gn3/auth/authentication/oauth2/models/oauth2token.py b/gn3/auth/authorisation/oauth2/oauth2token.py
index bfe4aaf..bb19039 100644
--- a/gn3/auth/authentication/oauth2/models/oauth2token.py
+++ b/gn3/auth/authorisation/oauth2/oauth2token.py
@@ -6,9 +6,9 @@ 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 gn3.auth.authorisation.users import User, user_by_id
 
 from .oauth2client import client, OAuth2Client
 
diff --git a/gn3/auth/authentication/oauth2/resource_server.py b/gn3/auth/authorisation/oauth2/resource_server.py
index 223e811..e806dc5 100644
--- a/gn3/auth/authentication/oauth2/resource_server.py
+++ b/gn3/auth/authorisation/oauth2/resource_server.py
@@ -5,7 +5,7 @@ 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
+from gn3.auth.authorisation.oauth2.oauth2token import token_by_access_token
 
 class BearerTokenValidator(_BearerTokenValidator):
     """Extends `authlib.oauth2.rfc6750.BearerTokenValidator`"""
diff --git a/gn3/auth/authorisation/privileges.py b/gn3/auth/authorisation/privileges.py
index dbb4129..7907d76 100644
--- a/gn3/auth/authorisation/privileges.py
+++ b/gn3/auth/authorisation/privileges.py
@@ -2,7 +2,7 @@
 from typing import Any, Iterable, NamedTuple
 
 from gn3.auth import db
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
 
 class Privilege(NamedTuple):
     """Class representing a privilege: creates immutable objects."""
diff --git a/gn3/auth/authorisation/resources/checks.py b/gn3/auth/authorisation/resources/checks.py
index fafde76..1f5a0f9 100644
--- a/gn3/auth/authorisation/resources/checks.py
+++ b/gn3/auth/authorisation/resources/checks.py
@@ -4,7 +4,7 @@ from functools import reduce
 from typing import Sequence
 
 from gn3.auth import db
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
 
 def __organise_privileges_by_resource_id__(rows):
     def __organise__(privs, row):
diff --git a/gn3/auth/authorisation/resources/models.py b/gn3/auth/authorisation/resources/models.py
index b301a93..cf7769e 100644
--- a/gn3/auth/authorisation/resources/models.py
+++ b/gn3/auth/authorisation/resources/models.py
@@ -7,7 +7,7 @@ from typing import Any, Dict, Sequence, Optional, NamedTuple
 
 from gn3.auth import db
 from gn3.auth.dictify import dictify
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
 from gn3.auth.db_utils import with_db_connection
 
 from .checks import authorised_for
diff --git a/gn3/auth/authorisation/resources/views.py b/gn3/auth/authorisation/resources/views.py
index 3b2bbeb..bda67cd 100644
--- a/gn3/auth/authorisation/resources/views.py
+++ b/gn3/auth/authorisation/resources/views.py
@@ -7,6 +7,8 @@ from functools import reduce
 from flask import request, jsonify, Response, Blueprint, current_app as app
 
 from gn3.auth.db_utils import with_db_connection
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User, user_by_id, user_by_email
 
 from .checks import authorised_for
 from .models import (
@@ -21,8 +23,6 @@ from ..groups.models import Group, GroupRole, group_role_by_id
 
 from ... import db
 from ...dictify import dictify
-from ...authentication.oauth2.resource_server import require_oauth
-from ...authentication.users import User, user_by_id, user_by_email
 
 resources = Blueprint("resources", __name__)
 
diff --git a/gn3/auth/authorisation/roles/models.py b/gn3/auth/authorisation/roles/models.py
index 97e11af..890d33b 100644
--- a/gn3/auth/authorisation/roles/models.py
+++ b/gn3/auth/authorisation/roles/models.py
@@ -7,7 +7,7 @@ from pymonad.either import Left, Right, Either
 
 from gn3.auth import db
 from gn3.auth.dictify import dictify
-from gn3.auth.authentication.users import User
+from gn3.auth.authorisation.users import User
 from gn3.auth.authorisation.errors import AuthorisationError
 
 from ..checks import authorised_p
diff --git a/gn3/auth/authorisation/roles/views.py b/gn3/auth/authorisation/roles/views.py
index 3670aab..d00e596 100644
--- a/gn3/auth/authorisation/roles/views.py
+++ b/gn3/auth/authorisation/roles/views.py
@@ -5,11 +5,10 @@ from flask import jsonify, Response, Blueprint, current_app
 
 from gn3.auth import db
 from gn3.auth.dictify import dictify
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
 
 from .models import user_role
 
-from ...authentication.oauth2.resource_server import require_oauth
-
 roles = Blueprint("roles", __name__)
 
 @roles.route("/view/<uuid:role_id>", methods=["GET"])
diff --git a/gn3/auth/authorisation/users/__init__.py b/gn3/auth/authorisation/users/__init__.py
index e69de29..5f0c89c 100644
--- a/gn3/auth/authorisation/users/__init__.py
+++ b/gn3/auth/authorisation/users/__init__.py
@@ -0,0 +1,12 @@
+"""Initialise the users' package."""
+from .base import (
+    User,
+    users,
+    save_user,
+    user_by_id,
+    # valid_login,
+    user_by_email,
+    hash_password, # only used in tests... maybe make gn-auth a GN3 dependency
+    same_password,
+    set_user_password
+)
diff --git a/gn3/auth/authorisation/users/admin/__init__.py b/gn3/auth/authorisation/users/admin/__init__.py
deleted file mode 100644
index 8aa0743..0000000
--- a/gn3/auth/authorisation/users/admin/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-"""The admin module"""
-from .views import admin
diff --git a/gn3/auth/authorisation/users/admin/ui.py b/gn3/auth/authorisation/users/admin/ui.py
deleted file mode 100644
index 242c7a6..0000000
--- a/gn3/auth/authorisation/users/admin/ui.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""UI utilities for the auth system."""
-from functools import wraps
-from flask import flash, url_for, redirect
-
-from gn3.auth.authentication.users import User
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.roles.models import user_roles
-
-from gn3.session import logged_in, session_user, clear_session_info
-
-def is_admin(func):
-    """Verify user is a system admin."""
-    @wraps(func)
-    @logged_in
-    def __admin__(*args, **kwargs):
-        admin_roles = [
-            role for role in with_db_connection(
-                lambda conn: user_roles(
-                    conn, User(**session_user())))
-            if role.role_name == "system-administrator"]
-        if len(admin_roles) > 0:
-            return func(*args, **kwargs)
-        flash("Expected a system administrator.", "alert-danger")
-        flash("You have been logged out of the system.", "alert-info")
-        clear_session_info()
-        return redirect(url_for("oauth2.admin.login"))
-    return __admin__
diff --git a/gn3/auth/authorisation/users/admin/views.py b/gn3/auth/authorisation/users/admin/views.py
deleted file mode 100644
index c9f1887..0000000
--- a/gn3/auth/authorisation/users/admin/views.py
+++ /dev/null
@@ -1,230 +0,0 @@
-"""UI for admin stuff"""
-import uuid
-import json
-import random
-import string
-from functools import partial
-from datetime import datetime, timezone, timedelta
-
-from email_validator import validate_email, EmailNotValidError
-from flask import (
-    flash,
-    request,
-    url_for,
-    redirect,
-    Blueprint,
-    current_app,
-    render_template)
-
-
-from gn3 import session
-from gn3.auth import db
-from gn3.auth.db_utils import with_db_connection
-
-from gn3.auth.authentication.oauth2.models.oauth2client import (
-    save_client,
-    OAuth2Client,
-    oauth2_clients,
-    client as oauth2_client,
-    delete_client as _delete_client)
-from gn3.auth.authentication.users import (
-    User,
-    user_by_id,
-    valid_login,
-    user_by_email,
-    hash_password)
-
-from .ui import is_admin
-
-admin = Blueprint("admin", __name__)
-
-@admin.before_request
-def update_expires():
-    """Update session expiration."""
-    if session.session_info() and not session.update_expiry():
-        flash("Session has expired. Logging out...", "alert-warning")
-        session.clear_session_info()
-        return redirect(url_for("oauth2.admin.login"))
-    return None
-
-@admin.route("/dashboard", methods=["GET"])
-@is_admin
-def dashboard():
-    """Admin dashboard."""
-    return render_template("admin/dashboard.html")
-
-@admin.route("/login", methods=["GET", "POST"])
-def login():
-    """Log in to GN3 directly without OAuth2 client."""
-    if request.method == "GET":
-        return render_template(
-            "admin/login.html",
-            next_uri=request.args.get("next", "oauth2.admin.dashboard"))
-
-    form = request.form
-    next_uri = form.get("next_uri", "oauth2.admin.dashboard")
-    error_message = "Invalid email or password provided."
-    login_page = redirect(url_for("oauth2.admin.login", next=next_uri))
-    try:
-        email = validate_email(form.get("email", "").strip(),
-                               check_deliverability=False)
-        password = form.get("password")
-        with db.connection(current_app.config["AUTH_DB"]) as conn:
-            user = user_by_email(conn, email["email"])
-            if valid_login(conn, user, password):
-                session.update_session_info(
-                    user=user._asdict(),
-                    expires=(
-                        datetime.now(tz=timezone.utc) + timedelta(minutes=10)))
-                return redirect(url_for(next_uri))
-            flash(error_message, "alert-danger")
-            return login_page
-    except EmailNotValidError as _enve:
-        flash(error_message, "alert-danger")
-        return login_page
-
-@admin.route("/logout", methods=["GET"])
-def logout():
-    """Log out the admin."""
-    if not session.session_info():
-        flash("Not logged in.", "alert-info")
-        return redirect(url_for("oauth2.admin.login"))
-    session.clear_session_info()
-    flash("Logged out", "alert-success")
-    return redirect(url_for("oauth2.admin.login"))
-
-def random_string(length: int = 64) -> str:
-    """Generate a random string."""
-    return "".join(
-        random.choice(string.ascii_letters + string.digits + string.punctuation)
-        for _idx in range(0, length))
-
-def __response_types__(grant_types: tuple[str, ...]) -> tuple[str, ...]:
-    """Compute response types from grant types."""
-    resps = {
-        "password": ("token",),
-        "authorization_code": ("token", "code"),
-        "refresh_token": ("token",)
-    }
-    return tuple(set(
-        resp_typ for types_list
-        in (types for grant, types in resps.items() if grant in grant_types)
-        for resp_typ in types_list))
-
-@admin.route("/register-client", methods=["GET", "POST"])
-@is_admin
-def register_client():
-    """Register an OAuth2 client."""
-    def __list_users__(conn):
-        with db.cursor(conn) as cursor:
-            cursor.execute("SELECT * FROM users")
-            return tuple(
-                User(uuid.UUID(row["user_id"]), row["email"], row["name"])
-                for row in cursor.fetchall())
-    if request.method == "GET":
-        return render_template(
-            "admin/register-client.html",
-            scope=current_app.config["OAUTH2_SCOPE"],
-            users=with_db_connection(__list_users__),
-            current_user=session.session_user())
-
-    form = request.form
-    raw_client_secret = random_string()
-    default_redirect_uri = form["redirect_uri"]
-    grant_types = form.getlist("grants[]")
-    client = OAuth2Client(
-        client_id = uuid.uuid4(),
-        client_secret = hash_password(raw_client_secret),
-        client_id_issued_at = datetime.now(tz=timezone.utc),
-        client_secret_expires_at = datetime.fromtimestamp(0),
-        client_metadata = {
-            "client_name": "GN2 Dev Server",
-            "token_endpoint_auth_method": [
-                "client_secret_post", "client_secret_basic"],
-            "client_type": "confidential",
-            "grant_types": ["password", "authorization_code", "refresh_token"],
-            "default_redirect_uri": default_redirect_uri,
-            "redirect_uris": [default_redirect_uri] + form.get("other_redirect_uri", "").split(),
-            "response_type": __response_types__(tuple(grant_types)),
-            "scope": form.getlist("scope[]")
-        },
-        user = with_db_connection(partial(
-            user_by_id, user_id=uuid.UUID(form["user"])))
-    )
-    client = with_db_connection(partial(save_client, the_client=client))
-    return render_template(
-        "admin/registered-client.html",
-        client=client,
-        client_secret = raw_client_secret)
-
-def __parse_client__(sqlite3_row) -> dict:
-    """Parse the client details into python datatypes."""
-    return {
-        **dict(sqlite3_row),
-        "client_metadata": json.loads(sqlite3_row["client_metadata"])
-    }
-
-@admin.route("/list-client", methods=["GET"])
-@is_admin
-def list_clients():
-    """List all registered OAuth2 clients."""
-    return render_template(
-        "admin/list-oauth2-clients.html",
-        clients=with_db_connection(oauth2_clients))
-
-@admin.route("/view-client/<uuid:client_id>", methods=["GET"])
-@is_admin
-def view_client(client_id: uuid.UUID):
-    """View details of OAuth2 client with given `client_id`."""
-    return render_template(
-        "admin/view-oauth2-client.html",
-        client=with_db_connection(partial(oauth2_client, client_id=client_id)),
-        scope=current_app.config["OAUTH2_SCOPE"])
-
-@admin.route("/edit-client", methods=["POST"])
-@is_admin
-def edit_client():
-    """Edit the details of the given client."""
-    form = request.form
-    the_client = with_db_connection(partial(
-        oauth2_client, client_id=uuid.UUID(form["client_id"])))
-    if the_client.is_nothing():
-        flash("No such client.", "alert-danger")
-        return redirect(url_for("oauth2.admin.list_clients"))
-    the_client = the_client.value
-    client_metadata = {
-        **the_client.client_metadata,
-        "default_redirect_uri": form["default_redirect_uri"],
-        "redirect_uris": list(set(
-            [form["default_redirect_uri"]] +
-            form["other_redirect_uris"].split("\r\n"))),
-        "grants": form.getlist("grants[]"),
-        "scope": form.getlist("scope[]")
-    }
-    with_db_connection(partial(save_client, the_client=OAuth2Client(
-        the_client.client_id,
-        the_client.client_secret,
-        the_client.client_id_issued_at,
-        the_client.client_secret_expires_at,
-        client_metadata,
-        the_client.user)))
-    flash("Client updated.", "alert-success")
-    return redirect(url_for("oauth2.admin.view_client",
-                            client_id=the_client.client_id))
-
-@admin.route("/delete-client", methods=["POST"])
-@is_admin
-def delete_client():
-    """Delete the details of the client."""
-    form = request.form
-    the_client = with_db_connection(partial(
-        oauth2_client, client_id=uuid.UUID(form["client_id"])))
-    if the_client.is_nothing():
-        flash("No such client.", "alert-danger")
-        return redirect(url_for("oauth2.admin.list_clients"))
-    the_client = the_client.value
-    with_db_connection(partial(_delete_client, client=the_client))
-    flash((f"Client '{the_client.client_metadata.client_name}' was deleted "
-           "successfully."),
-          "alert-success")
-    return redirect(url_for("oauth2.admin.list_clients"))
diff --git a/gn3/auth/authentication/users.py b/gn3/auth/authorisation/users/base.py
index 0e72ed2..0e72ed2 100644
--- a/gn3/auth/authentication/users.py
+++ b/gn3/auth/authorisation/users/base.py
diff --git a/gn3/auth/authorisation/users/collections/views.py b/gn3/auth/authorisation/users/collections/views.py
index 1fa25a3..775e8bc 100644
--- a/gn3/auth/authorisation/users/collections/views.py
+++ b/gn3/auth/authorisation/users/collections/views.py
@@ -9,8 +9,8 @@ from gn3.auth.db_utils import with_db_connection
 from gn3.auth.authorisation.checks import require_json
 from gn3.auth.authorisation.errors import NotFoundError
 
-from gn3.auth.authentication.users import User, user_by_id
-from gn3.auth.authentication.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User, user_by_id
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
 
 from .models import (
     add_traits,
diff --git a/gn3/auth/authorisation/users/masquerade/__init__.py b/gn3/auth/authorisation/users/masquerade/__init__.py
deleted file mode 100644
index 69d64f0..0000000
--- a/gn3/auth/authorisation/users/masquerade/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""Package to deal with masquerading."""
diff --git a/gn3/auth/authorisation/users/masquerade/models.py b/gn3/auth/authorisation/users/masquerade/models.py
deleted file mode 100644
index 9f24b6b..0000000
--- a/gn3/auth/authorisation/users/masquerade/models.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""Functions for handling masquerade."""
-from uuid import uuid4
-from functools import wraps
-from datetime import datetime
-
-from flask import current_app as app
-
-from gn3.auth import db
-
-from gn3.auth.authorisation.errors import ForbiddenAccess
-from gn3.auth.authorisation.roles.models import user_roles
-
-from gn3.auth.authentication.users import User
-from gn3.auth.authentication.oauth2.models.oauth2token import (
-    OAuth2Token, save_token)
-
-__FIVE_HOURS__ = (60 * 60 * 5)
-
-def can_masquerade(func):
-    """Security decorator."""
-    @wraps(func)
-    def __checker__(*args, **kwargs):
-        if len(args) == 3:
-            conn, token, _masq_user = args
-        elif len(args) == 2:
-            conn, token = args
-        elif len(args) == 1:
-            conn = args[0]
-            token = kwargs["original_token"]
-        else:
-            conn = kwargs["conn"]
-            token = kwargs["original_token"]
-
-        masq_privs = [priv for role in user_roles(conn, token.user)
-                      for priv in role.privileges
-                      if priv.privilege_id == "system:user:masquerade"]
-        if len(masq_privs) == 0:
-            raise ForbiddenAccess(
-                "You do not have the ability to masquerade as another user.")
-        return func(*args, **kwargs)
-    return __checker__
-
-@can_masquerade
-def masquerade_as(
-        conn: db.DbConnection,
-        original_token: OAuth2Token,
-        masqueradee: User) -> OAuth2Token:
-    """Get a token that enables `masquerader` to act as `masqueradee`."""
-    token_details = app.config["OAUTH2_SERVER"].generate_token(
-        client=original_token.client,
-        grant_type="authorization_code",
-        user=masqueradee,
-        expires_in=__FIVE_HOURS__,
-        include_refresh_token=True)
-    new_token = OAuth2Token(
-        token_id=uuid4(),
-        client=original_token.client,
-        token_type=token_details["token_type"],
-        access_token=token_details["access_token"],
-        refresh_token=token_details.get("refresh_token"),
-        scope=original_token.scope,
-        revoked=False,
-        issued_at=datetime.now(),
-        expires_in=token_details["expires_in"],
-        user=masqueradee)
-    save_token(conn, new_token)
-    return new_token
diff --git a/gn3/auth/authorisation/users/masquerade/views.py b/gn3/auth/authorisation/users/masquerade/views.py
deleted file mode 100644
index 43286a1..0000000
--- a/gn3/auth/authorisation/users/masquerade/views.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Endpoints for user masquerade"""
-from uuid import UUID
-from functools import partial
-
-from flask import request, jsonify, Response, Blueprint
-
-from gn3.auth.db_utils import with_db_connection
-from gn3.auth.authorisation.errors import InvalidData
-from gn3.auth.authorisation.checks import require_json
-
-from gn3.auth.authentication.users import user_by_id
-from gn3.auth.authentication.oauth2.resource_server import require_oauth
-
-from .models import masquerade_as
-
-masq = Blueprint("masquerade", __name__)
-
-@masq.route("/", methods=["POST"])
-@require_oauth("profile user masquerade")
-@require_json
-def masquerade() -> Response:
-    """Masquerade as a particular user."""
-    with require_oauth.acquire("profile user masquerade") as token:
-        masqueradee_id = UUID(request.json["masquerade_as"])#type: ignore[index]
-        if masqueradee_id == token.user.user_id:
-            raise InvalidData("You are not allowed to masquerade as yourself.")
-
-        masq_user = with_db_connection(partial(
-            user_by_id, user_id=masqueradee_id))
-        def __masq__(conn):
-            new_token = masquerade_as(conn, original_token=token, masqueradee=masq_user)
-            return new_token
-        def __dump_token__(tok):
-            return {
-                key: value for key, value in (tok._asdict().items())
-                if key in ("access_token", "refresh_token", "expires_in",
-                           "token_type")
-            }
-        return jsonify({
-            "original": {
-                "user": token.user._asdict(),
-                "token": __dump_token__(token)
-            },
-            "masquerade_as": {
-                "user": masq_user._asdict(),
-                "token": __dump_token__(with_db_connection(__masq__))
-            }
-        })
diff --git a/gn3/auth/authorisation/users/models.py b/gn3/auth/authorisation/users/models.py
index 89c1d22..0157154 100644
--- a/gn3/auth/authorisation/users/models.py
+++ b/gn3/auth/authorisation/users/models.py
@@ -7,7 +7,7 @@ from gn3.auth.authorisation.roles.models import Role
 from gn3.auth.authorisation.checks import authorised_p
 from gn3.auth.authorisation.privileges import Privilege
 
-from gn3.auth.authentication.users import User
+from .base import User
 
 @authorised_p(
     ("system:user:list",),
diff --git a/gn3/auth/authorisation/users/views.py b/gn3/auth/authorisation/users/views.py
index 826e222..f75b51e 100644
--- a/gn3/auth/authorisation/users/views.py
+++ b/gn3/auth/authorisation/users/views.py
@@ -10,9 +10,11 @@ from flask import request, jsonify, Response, Blueprint, current_app
 from gn3.auth import db
 from gn3.auth.dictify import dictify
 from gn3.auth.db_utils import with_db_connection
+from gn3.auth.authorisation.oauth2.resource_server import require_oauth
+from gn3.auth.authorisation.users import User, save_user, set_user_password
+from gn3.auth.authorisation.oauth2.oauth2token import token_by_access_token
 
 from .models import list_users
-from .masquerade.views import masq
 from .collections.views import collections
 
 from ..groups.models import user_group as _user_group
@@ -21,12 +23,7 @@ from ..roles.models import assign_default_roles, user_roles as _user_roles
 from ..errors import (
     NotFoundError, UsernameError, PasswordError, UserRegistrationError)
 
-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
-
 users = Blueprint("users", __name__)
-users.register_blueprint(masq, url_prefix="/masquerade")
 users.register_blueprint(collections, url_prefix="/collections")
 
 @users.route("/", methods=["GET"])
diff --git a/gn3/auth/views.py b/gn3/auth/views.py
index 56eace7..da64049 100644
--- a/gn3/auth/views.py
+++ b/gn3/auth/views.py
@@ -1,21 +1,16 @@
 """The Auth(oris|entic)ation routes/views"""
 from flask import Blueprint
 
-from .authentication.oauth2.views import auth
-
 from .authorisation.data.views import data
 from .authorisation.users.views import users
-from .authorisation.users.admin import admin
 from .authorisation.roles.views import roles
 from .authorisation.groups.views import groups
 from .authorisation.resources.views import resources
 
 oauth2 = Blueprint("oauth2", __name__)
 
-oauth2.register_blueprint(auth, url_prefix="/")
 oauth2.register_blueprint(data, url_prefix="/data")
 oauth2.register_blueprint(users, url_prefix="/user")
 oauth2.register_blueprint(roles, url_prefix="/role")
-oauth2.register_blueprint(admin, url_prefix="/admin")
 oauth2.register_blueprint(groups, url_prefix="/group")
 oauth2.register_blueprint(resources, url_prefix="/resource")
diff --git a/main.py b/main.py
index d74f3aa..3a6a6d4 100644
--- a/main.py
+++ b/main.py
@@ -3,7 +3,6 @@ import sys
 import uuid
 import json
 from math import ceil
-from pathlib import Path
 from datetime import datetime
 
 
@@ -12,13 +11,10 @@ from yoyo import get_backend, read_migrations
 
 from gn3 import migrations
 from gn3.app import create_app
-from gn3.auth.authentication.users import hash_password
+from gn3.auth.authorisation.users import hash_password
 
 from gn3.auth import db
 
-from scripts import register_sys_admin as rsysadm# type: ignore[import]
-from scripts import migrate_existing_data as med# type: ignore[import]
-
 app = create_app()
 
 ##### BEGIN: CLI Commands #####
@@ -112,16 +108,6 @@ def assign_system_admin(user_id: uuid.UUID):
               file=sys.stderr)
         sys.exit(1)
 
-@app.cli.command()
-def make_data_public():
-    """Make existing data that is not assigned to any group publicly visible."""
-    med.entry(app.config["AUTH_DB"], app.config["SQL_URI"])
-
-@app.cli.command()
-def register_admin():
-    """Register the administrator."""
-    rsysadm.register_admin(Path(app.config["AUTH_DB"]))
-
 ##### END: CLI Commands #####
 
 if __name__ == '__main__':
diff --git a/migrations/auth/20221103_01_js9ub-initialise-the-auth-entic-oris-ation-database.py b/migrations/auth/20221103_01_js9ub-initialise-the-auth-entic-oris-ation-database.py
deleted file mode 100644
index d511f5d..0000000
--- a/migrations/auth/20221103_01_js9ub-initialise-the-auth-entic-oris-ation-database.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
-Initialise the auth(entic|oris)ation database.
-"""
-
-from yoyo import step
-
-__depends__ = {} # type: ignore[var-annotated]
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS users(
-            user_id TEXT PRIMARY KEY NOT NULL,
-            email TEXT UNIQUE NOT NULL,
-            name TEXT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS users")
-]
diff --git a/migrations/auth/20221103_02_sGrIs-create-user-credentials-table.py b/migrations/auth/20221103_02_sGrIs-create-user-credentials-table.py
deleted file mode 100644
index 48bd663..0000000
--- a/migrations/auth/20221103_02_sGrIs-create-user-credentials-table.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""
-create user_credentials table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221103_01_js9ub-initialise-the-auth-entic-oris-ation-database'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS user_credentials(
-            user_id TEXT PRIMARY KEY,
-            password TEXT NOT NULL,
-            FOREIGN KEY(user_id) REFERENCES users(user_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS user_credentials")
-]
diff --git a/migrations/auth/20221108_01_CoxYh-create-the-groups-table.py b/migrations/auth/20221108_01_CoxYh-create-the-groups-table.py
deleted file mode 100644
index 29f92d4..0000000
--- a/migrations/auth/20221108_01_CoxYh-create-the-groups-table.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
-Create the groups table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221103_02_sGrIs-create-user-credentials-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS groups(
-            group_id TEXT PRIMARY KEY NOT NULL,
-            group_name TEXT NOT NULL,
-            group_metadata TEXT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS groups")
-]
diff --git a/migrations/auth/20221108_02_wxTr9-create-privileges-table.py b/migrations/auth/20221108_02_wxTr9-create-privileges-table.py
deleted file mode 100644
index 67720b2..0000000
--- a/migrations/auth/20221108_02_wxTr9-create-privileges-table.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-Create privileges table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221108_01_CoxYh-create-the-groups-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE privileges(
-            privilege_id TEXT PRIMARY KEY,
-            privilege_name TEXT NOT NULL
-        ) WITHOUT ROWID
-        """,
-         "DROP TABLE IF EXISTS privileges")
-]
diff --git a/migrations/auth/20221108_03_Pbhb1-create-resource-categories-table.py b/migrations/auth/20221108_03_Pbhb1-create-resource-categories-table.py
deleted file mode 100644
index ce752ef..0000000
--- a/migrations/auth/20221108_03_Pbhb1-create-resource-categories-table.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
-Create resource_categories table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221108_02_wxTr9-create-privileges-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE resource_categories(
-            resource_category_id TEXT PRIMARY KEY,
-            resource_category_key TEXT NOT NULL,
-            resource_category_description TEXT NOT NULL
-        ) WITHOUT ROWID
-        """,
-    "DROP TABLE IF EXISTS resource_categories")
-]
diff --git a/migrations/auth/20221108_04_CKcSL-init-data-in-resource-categories-table.py b/migrations/auth/20221108_04_CKcSL-init-data-in-resource-categories-table.py
deleted file mode 100644
index 76ffbef..0000000
--- a/migrations/auth/20221108_04_CKcSL-init-data-in-resource-categories-table.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""
-Init data in resource_categories table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221108_03_Pbhb1-create-resource-categories-table'}
-
-steps = [
-    step(
-        """
-        INSERT INTO resource_categories VALUES
-        ('fad071a3-2fc8-40b8-992b-cdefe7dcac79', 'mrna', 'mRNA Dataset'),
-        ('548d684b-d4d1-46fb-a6d3-51a56b7da1b3', 'phenotype', 'Phenotype (Publish) Dataset'),
-        ('48056f84-a2a6-41ac-8319-0e1e212cba2a', 'genotype', 'Genotype Dataset')
-        """,
-        """
-        DELETE FROM resource_categories WHERE resource_category_id IN
-        (
-            'fad071a3-2fc8-40b8-992b-cdefe7dcac79',
-            '548d684b-d4d1-46fb-a6d3-51a56b7da1b3',
-            '48056f84-a2a6-41ac-8319-0e1e212cba2a'
-        )
-        """)
-]
diff --git a/migrations/auth/20221109_01_HbD5F-add-resource-meta-field-to-resource-categories-field.py b/migrations/auth/20221109_01_HbD5F-add-resource-meta-field-to-resource-categories-field.py
deleted file mode 100644
index 6c829b1..0000000
--- a/migrations/auth/20221109_01_HbD5F-add-resource-meta-field-to-resource-categories-field.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""
-Add 'resource_meta' field to 'resource_categories' field.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221108_04_CKcSL-init-data-in-resource-categories-table'}
-
-steps = [
-    step(
-        """
-        ALTER TABLE resource_categories
-        ADD COLUMN
-            resource_meta TEXT NOT NULL DEFAULT '[]'
-        """,
-        "ALTER TABLE resource_categories DROP COLUMN resource_meta")
-]
diff --git a/migrations/auth/20221110_01_WtZ1I-create-resources-table.py b/migrations/auth/20221110_01_WtZ1I-create-resources-table.py
deleted file mode 100644
index abc8895..0000000
--- a/migrations/auth/20221110_01_WtZ1I-create-resources-table.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""
-Create 'resources' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221109_01_HbD5F-add-resource-meta-field-to-resource-categories-field'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS resources(
-            group_id TEXT NOT NULL,
-            resource_id TEXT NOT NULL,
-            resource_name TEXT NOT NULL UNIQUE,
-            resource_category_id TEXT NOT NULL,
-            PRIMARY KEY(group_id, resource_id),
-            FOREIGN KEY(group_id) REFERENCES groups(group_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(resource_category_id)
-              REFERENCES resource_categories(resource_category_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS resources")
-]
diff --git a/migrations/auth/20221110_05_BaNtL-create-roles-table.py b/migrations/auth/20221110_05_BaNtL-create-roles-table.py
deleted file mode 100644
index 51e19e8..0000000
--- a/migrations/auth/20221110_05_BaNtL-create-roles-table.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
-Create 'roles' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221110_01_WtZ1I-create-resources-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS roles(
-            role_id TEXT NOT NULL PRIMARY KEY,
-            role_name TEXT NOT NULL,
-            user_editable INTEGER NOT NULL DEFAULT 1 CHECK (user_editable=0 or user_editable=1)
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS roles")
-]
diff --git a/migrations/auth/20221110_06_Pq2kT-create-generic-roles-table.py b/migrations/auth/20221110_06_Pq2kT-create-generic-roles-table.py
deleted file mode 100644
index 2b55c2b..0000000
--- a/migrations/auth/20221110_06_Pq2kT-create-generic-roles-table.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""
-Create 'generic_roles' table
-
-The roles in this table will be template roles, defining some common roles that
-can be used within the groups.
-
-They could also be used to define system-level roles, though those will not be
-provided to the "common" users.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221110_05_BaNtL-create-roles-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS generic_roles(
-            role_id TEXT PRIMARY KEY,
-            role_name TEXT NOT NULL
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS generic_roles")
-]
diff --git a/migrations/auth/20221110_07_7WGa1-create-role-privileges-table.py b/migrations/auth/20221110_07_7WGa1-create-role-privileges-table.py
deleted file mode 100644
index 0d0eeb9..0000000
--- a/migrations/auth/20221110_07_7WGa1-create-role-privileges-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create 'role_privileges' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221110_06_Pq2kT-create-generic-roles-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS role_privileges(
-            role_id TEXT NOT NULL,
-            privilege_id TEXT NOT NULL,
-            PRIMARY KEY(role_id, privilege_id),
-            FOREIGN KEY(role_id) REFERENCES roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(privilege_id) REFERENCES privileges(privilege_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS role_privileges"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS idx_tbl_role_privileges_cols_role_id
-        ON role_privileges(role_id)
-        """,
-        "DROP INDEX IF EXISTS idx_tbl_role_privileges_cols_role_id")
-]
diff --git a/migrations/auth/20221110_08_23psB-add-privilege-category-and-privilege-description-columns-to-privileges-table.py b/migrations/auth/20221110_08_23psB-add-privilege-category-and-privilege-description-columns-to-privileges-table.py
deleted file mode 100644
index 077182b..0000000
--- a/migrations/auth/20221110_08_23psB-add-privilege-category-and-privilege-description-columns-to-privileges-table.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-Add 'privilege_category' and 'privilege_description' columns to 'privileges' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221110_07_7WGa1-create-role-privileges-table'}
-
-steps = [
-    step(
-        """
-        ALTER TABLE privileges ADD COLUMN
-            privilege_category TEXT NOT NULL DEFAULT 'common'
-        """,
-        "ALTER TABLE privileges DROP COLUMN privilege_category"),
-    step(
-        """
-        ALTER TABLE privileges ADD COLUMN
-            privilege_description TEXT
-        """,
-        "ALTER TABLE privileges DROP COLUMN privilege_description")
-]
diff --git a/migrations/auth/20221113_01_7M0hv-enumerate-initial-privileges.py b/migrations/auth/20221113_01_7M0hv-enumerate-initial-privileges.py
deleted file mode 100644
index 072f226..0000000
--- a/migrations/auth/20221113_01_7M0hv-enumerate-initial-privileges.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
-Enumerate initial privileges
-"""
-
-from yoyo import step
-
-__depends__ = {'20221110_08_23psB-add-privilege-category-and-privilege-description-columns-to-privileges-table'}
-
-steps = [
-    step(
-        """
-        INSERT INTO
-            privileges(privilege_id, privilege_name, privilege_category,
-                       privilege_description)
-        VALUES
-            -- group-management privileges
-            ('4842e2aa-38b9-4349-805e-0a99a9cf8bff', 'create-group',
-             'group-management', 'Create a group'),
-            ('3ebfe79c-d159-4629-8b38-772cf4bc2261', 'view-group',
-             'group-management', 'View the details of a group'),
-            ('52576370-b3c7-4e6a-9f7e-90e9dbe24d8f', 'edit-group',
-             'group-management', 'Edit the details of a group'),
-            ('13ec2a94-4f1a-442d-aad2-936ad6dd5c57', 'delete-group',
-             'group-management', 'Delete a group'),
-            ('ae4add8c-789a-4d11-a6e9-a306470d83d9', 'add-group-member',
-             'group-management', 'Add a user to a group'),
-            ('f1bd3f42-567e-4965-9643-6d1a52ddee64', 'remove-group-member',
-             'group-management', 'Remove a user from a group'),
-            ('80f11285-5079-4ec0-907c-06509f88a364', 'assign-group-leader',
-             'group-management', 'Assign user group-leader privileges'),
-            ('d4afe2b3-4ca0-4edd-b37d-966535b5e5bd',
-             'transfer-group-leadership', 'group-management',
-             'Transfer leadership of the group to some other member'),
-
-            -- resource-management privileges
-            ('aa25b32a-bff2-418d-b0a2-e26b4a8f089b', 'create-resource',
-             'resource-management', 'Create a resource object'),
-            ('7f261757-3211-4f28-a43f-a09b800b164d', 'view-resource',
-             'resource-management', 'view a resource and use it in computations'),
-            ('2f980855-959b-4339-b80e-25d1ec286e21', 'edit-resource',
-             'resource-management', 'edit/update a resource'),
-            ('d2a070fd-e031-42fb-ba41-d60cf19e5d6d', 'delete-resource',
-             'resource-management', 'Delete a resource'),
-
-            -- role-management privileges
-            ('221660b1-df05-4be1-b639-f010269dbda9', 'create-role',
-             'role-management', 'Create a new role'),
-            ('7bcca363-cba9-4169-9e31-26bdc6179b28', 'edit-role',
-             'role-management', 'edit/update an existing role'),
-            ('5103cc68-96f8-4ebb-83a4-a31692402c9b', 'assign-role',
-             'role-management', 'Assign a role to an existing user'),
-            ('1c59eff5-9336-4ed2-a166-8f70d4cb012e', 'delete-role',
-             'role-management', 'Delete an existing role'),
-
-            -- user-management privileges
-            ('e7252301-6ee0-43ba-93ef-73b607cf06f6', 'reset-any-password',
-             'user-management', 'Reset the password for any user'),
-            ('1fe61370-cae9-4983-bd6c-ce61050c510f', 'delete-any-user',
-             'user-management', 'Delete any user from the system'),
-
-            -- sytem-admin privileges
-            ('519db546-d44e-4fdc-9e4e-25aa67548ab3', 'masquerade',
-             'system-admin', 'Masquerade as some other user')
-        """,
-        "DELETE FROM privileges")
-]
diff --git a/migrations/auth/20221114_01_n8gsF-create-generic-role-privileges-table.py b/migrations/auth/20221114_01_n8gsF-create-generic-role-privileges-table.py
deleted file mode 100644
index 2048f4a..0000000
--- a/migrations/auth/20221114_01_n8gsF-create-generic-role-privileges-table.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""
-Create 'generic_role_privileges' table
-
-This table links the generic_roles to the privileges they provide
-"""
-
-from yoyo import step
-
-__depends__ = {'20221113_01_7M0hv-enumerate-initial-privileges'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS generic_role_privileges(
-            generic_role_id TEXT NOT NULL,
-            privilege_id TEXT NOT NULL,
-            PRIMARY KEY(generic_role_id, privilege_id),
-            FOREIGN KEY(generic_role_id) REFERENCES generic_roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(privilege_id) REFERENCES privileges(privilege_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS generic_role_privileges"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS
-            idx_tbl_generic_role_privileges_cols_generic_role_id
-        ON generic_role_privileges(generic_role_id)
-        """,
-        """
-        DROP INDEX IF EXISTS
-            idx_tbl_generic_role_privileges_cols_generic_role_id
-        """)
-]
diff --git a/migrations/auth/20221114_02_DKKjn-drop-generic-role-tables.py b/migrations/auth/20221114_02_DKKjn-drop-generic-role-tables.py
deleted file mode 100644
index 6bd101b..0000000
--- a/migrations/auth/20221114_02_DKKjn-drop-generic-role-tables.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
-Drop 'generic_role*' tables
-"""
-
-from yoyo import step
-
-__depends__ = {'20221114_01_n8gsF-create-generic-role-privileges-table'}
-
-steps = [
-    step(
-        """
-        DROP INDEX IF EXISTS
-            idx_tbl_generic_role_privileges_cols_generic_role_id
-        """,
-        """
-        CREATE INDEX IF NOT EXISTS
-            idx_tbl_generic_role_privileges_cols_generic_role_id
-        ON generic_role_privileges(generic_role_id)
-        """),
-    step(
-        "DROP TABLE IF EXISTS generic_role_privileges",
-        """
-        CREATE TABLE IF NOT EXISTS generic_role_privileges(
-            generic_role_id TEXT NOT NULL,
-            privilege_id TEXT NOT NULL,
-            PRIMARY KEY(generic_role_id, privilege_id),
-            FOREIGN KEY(generic_role_id) REFERENCES generic_roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(privilege_id) REFERENCES privileges(privilege_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """),
-    step(
-        "DROP TABLE IF EXISTS generic_roles",
-        """
-        CREATE TABLE IF NOT EXISTS generic_roles(
-            role_id TEXT PRIMARY KEY,
-            role_name TEXT NOT NULL
-        ) WITHOUT ROWID
-        """)
-]
diff --git a/migrations/auth/20221114_03_PtWjc-create-group-roles-table.py b/migrations/auth/20221114_03_PtWjc-create-group-roles-table.py
deleted file mode 100644
index a7e7b45..0000000
--- a/migrations/auth/20221114_03_PtWjc-create-group-roles-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create 'group_roles' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221114_02_DKKjn-drop-generic-role-tables'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS group_roles(
-            group_id TEXT NOT NULL,
-            role_id TEXT NOT NULL,
-            PRIMARY KEY(group_id, role_id),
-            FOREIGN KEY(group_id) REFERENCES groups(group_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(role_id) REFERENCES roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS group_roles"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS idx_tbl_group_roles_cols_group_id
-        ON group_roles(group_id)
-        """,
-        "DROP INDEX IF EXISTS idx_tbl_group_roles_cols_group_id")
-]
diff --git a/migrations/auth/20221114_04_tLUzB-initialise-basic-roles.py b/migrations/auth/20221114_04_tLUzB-initialise-basic-roles.py
deleted file mode 100644
index 386f481..0000000
--- a/migrations/auth/20221114_04_tLUzB-initialise-basic-roles.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
-Initialise basic roles
-"""
-
-from yoyo import step
-
-__depends__ = {'20221114_03_PtWjc-create-group-roles-table'}
-
-steps = [
-    step(
-        """
-        INSERT INTO roles(role_id, role_name, user_editable) VALUES
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30', 'group-leader', '0'),
-            ('522e4d40-aefc-4a64-b7e0-768b8be517ee', 'resource-owner', '0')
-        """,
-        "DELETE FROM roles"),
-    step(
-        """
-        INSERT INTO role_privileges(role_id, privilege_id)
-        VALUES
-            -- group-management
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '4842e2aa-38b9-4349-805e-0a99a9cf8bff'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '3ebfe79c-d159-4629-8b38-772cf4bc2261'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '52576370-b3c7-4e6a-9f7e-90e9dbe24d8f'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '13ec2a94-4f1a-442d-aad2-936ad6dd5c57'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             'ae4add8c-789a-4d11-a6e9-a306470d83d9'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             'f1bd3f42-567e-4965-9643-6d1a52ddee64'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             'd4afe2b3-4ca0-4edd-b37d-966535b5e5bd'),
-
-            -- resource-management
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             'aa25b32a-bff2-418d-b0a2-e26b4a8f089b'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '7f261757-3211-4f28-a43f-a09b800b164d'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '2f980855-959b-4339-b80e-25d1ec286e21'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             'd2a070fd-e031-42fb-ba41-d60cf19e5d6d'),
-            ('522e4d40-aefc-4a64-b7e0-768b8be517ee',
-             'aa25b32a-bff2-418d-b0a2-e26b4a8f089b'),
-            ('522e4d40-aefc-4a64-b7e0-768b8be517ee',
-             '7f261757-3211-4f28-a43f-a09b800b164d'),
-            ('522e4d40-aefc-4a64-b7e0-768b8be517ee',
-             '2f980855-959b-4339-b80e-25d1ec286e21'),
-            ('522e4d40-aefc-4a64-b7e0-768b8be517ee',
-             'd2a070fd-e031-42fb-ba41-d60cf19e5d6d')
-        """,
-        "DELETE FROM role_privileges")
-]
diff --git a/migrations/auth/20221114_05_hQun6-create-user-roles-table.py b/migrations/auth/20221114_05_hQun6-create-user-roles-table.py
deleted file mode 100644
index e0de751..0000000
--- a/migrations/auth/20221114_05_hQun6-create-user-roles-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create 'user_roles' table.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221114_04_tLUzB-initialise-basic-roles'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS user_roles(
-            user_id TEXT NOT NULL,
-            role_id TEXT NOT NULL,
-            PRIMARY KEY(user_id, role_id),
-            FOREIGN KEY(user_id) REFERENCES users(user_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(role_id) REFERENCES roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS user_roles"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS idx_tbl_user_roles_cols_user_id
-        ON user_roles(user_id)
-        """,
-        "DROP INDEX IF EXISTS idx_tbl_user_roles_cols_user_id")
-]
diff --git a/migrations/auth/20221116_01_nKUmX-add-privileges-to-group-leader-role.py b/migrations/auth/20221116_01_nKUmX-add-privileges-to-group-leader-role.py
deleted file mode 100644
index 2e4ae28..0000000
--- a/migrations/auth/20221116_01_nKUmX-add-privileges-to-group-leader-role.py
+++ /dev/null
@@ -1,35 +0,0 @@
-"""
-Add privileges to 'group-leader' role.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221114_05_hQun6-create-user-roles-table'}
-
-steps = [
-    step(
-        """
-        INSERT INTO role_privileges(role_id, privilege_id)
-        VALUES
-            -- role management
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '221660b1-df05-4be1-b639-f010269dbda9'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '7bcca363-cba9-4169-9e31-26bdc6179b28'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '5103cc68-96f8-4ebb-83a4-a31692402c9b'),
-            ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-             '1c59eff5-9336-4ed2-a166-8f70d4cb012e')
-        """,
-        """
-        DELETE FROM role_privileges
-        WHERE
-            role_id='a0e67630-d502-4b9f-b23f-6805d0f30e30'
-        AND privilege_id IN (
-            '221660b1-df05-4be1-b639-f010269dbda9',
-            '7bcca363-cba9-4169-9e31-26bdc6179b28',
-            '5103cc68-96f8-4ebb-83a4-a31692402c9b',
-            '1c59eff5-9336-4ed2-a166-8f70d4cb012e'
-        )
-        """)
-]
diff --git a/migrations/auth/20221117_01_RDlfx-modify-group-roles-add-group-role-id.py b/migrations/auth/20221117_01_RDlfx-modify-group-roles-add-group-role-id.py
deleted file mode 100644
index a4d7806..0000000
--- a/migrations/auth/20221117_01_RDlfx-modify-group-roles-add-group-role-id.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-Modify 'group_roles': add 'group_role_id'
-
-At this point, there is no data in the `group_roles` table  and therefore, it
-should be safe to simply recreate it.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221116_01_nKUmX-add-privileges-to-group-leader-role'}
-
-steps = [
-    step(
-        "DROP INDEX IF EXISTS idx_tbl_group_roles_cols_group_id",
-        """
-        CREATE INDEX IF NOT EXISTS idx_tbl_group_roles_cols_group_id
-        ON group_roles(group_id)
-        """),
-    step(
-        "DROP TABLE IF EXISTS group_roles",
-        """
-        CREATE TABLE IF NOT EXISTS group_roles(
-            group_id TEXT NOT NULL,
-            role_id TEXT NOT NULL,
-            PRIMARY KEY(group_id, role_id),
-            FOREIGN KEY(group_id) REFERENCES groups(group_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(role_id) REFERENCES roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """),
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS group_roles(
-            group_role_id TEXT PRIMARY KEY,
-            group_id TEXT NOT NULL,
-            role_id TEXT NOT NULL,
-            UNIQUE (group_id, role_id),
-            FOREIGN KEY(group_id) REFERENCES groups(group_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY(role_id) REFERENCES roles(role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS group_roles"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS idx_tbl_group_roles_cols_group_id
-        ON group_roles(group_id)
-        """,
-        "DROP INDEX IF EXISTS idx_tbl_group_roles_cols_group_id")
-]
diff --git a/migrations/auth/20221117_02_fmuZh-create-group-users-table.py b/migrations/auth/20221117_02_fmuZh-create-group-users-table.py
deleted file mode 100644
index 92885ef..0000000
--- a/migrations/auth/20221117_02_fmuZh-create-group-users-table.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""
-Create 'group_users' table.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221117_01_RDlfx-modify-group-roles-add-group-role-id'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS group_users(
-            group_id TEXT NOT NULL,
-            user_id TEXT NOT NULL UNIQUE, -- user can only be in one group
-            PRIMARY KEY(group_id, user_id)
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS group_users"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS tbl_group_users_cols_group_id
-        ON group_users(group_id)
-        """,
-        "DROP INDEX IF EXISTS tbl_group_users_cols_group_id")
-]
diff --git a/migrations/auth/20221206_01_BbeF9-create-group-user-roles-on-resources-table.py b/migrations/auth/20221206_01_BbeF9-create-group-user-roles-on-resources-table.py
deleted file mode 100644
index 9aa3667..0000000
--- a/migrations/auth/20221206_01_BbeF9-create-group-user-roles-on-resources-table.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
-Create 'group_user_roles_on_resources' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221117_02_fmuZh-create-group-users-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE group_user_roles_on_resources (
-            group_id TEXT NOT NULL,
-            user_id TEXT NOT NULL,
-            role_id TEXT NOT NULL,
-            resource_id TEXT NOT NULL,
-            PRIMARY KEY (group_id, user_id, role_id, resource_id),
-            FOREIGN KEY (user_id)
-              REFERENCES users(user_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY (group_id, role_id)
-              REFERENCES group_roles(group_id, role_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY (group_id, resource_id)
-              REFERENCES resources(group_id, resource_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS group_user_roles_on_resources"),
-    step(
-        """
-        CREATE INDEX IF NOT EXISTS
-            idx_tbl_group_user_roles_on_resources_group_user_resource
-        ON group_user_roles_on_resources(group_id, user_id, resource_id)
-        """,
-        """
-        DROP INDEX IF EXISTS
-            idx_tbl_group_user_roles_on_resources_group_user_resource""")
-]
diff --git a/migrations/auth/20221208_01_sSdHz-add-public-column-to-resources-table.py b/migrations/auth/20221208_01_sSdHz-add-public-column-to-resources-table.py
deleted file mode 100644
index 2238069..0000000
--- a/migrations/auth/20221208_01_sSdHz-add-public-column-to-resources-table.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
-Add 'public' column to 'resources' table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221206_01_BbeF9-create-group-user-roles-on-resources-table'}
-
-steps = [
-    step(
-        """
-        ALTER TABLE resources ADD COLUMN
-            public INTEGER NOT NULL DEFAULT 0 CHECK (public=0 or public=1)
-        """,
-        "ALTER TABLE resources DROP COLUMN public")
-]
diff --git a/migrations/auth/20221219_01_CI3tN-create-oauth2-clients-table.py b/migrations/auth/20221219_01_CI3tN-create-oauth2-clients-table.py
deleted file mode 100644
index 475be01..0000000
--- a/migrations/auth/20221219_01_CI3tN-create-oauth2-clients-table.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""
-create oauth2_clients table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221208_01_sSdHz-add-public-column-to-resources-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS oauth2_clients(
-            client_id TEXT NOT NULL,
-            client_secret TEXT NOT NULL,
-            client_id_issued_at INTEGER NOT NULL,
-            client_secret_expires_at INTEGER NOT NULL,
-            client_metadata TEXT,
-            user_id TEXT NOT NULL,
-            PRIMARY KEY(client_id),
-            FOREIGN KEY(user_id) REFERENCES users(user_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS oauth2_clients")
-]
diff --git a/migrations/auth/20221219_02_buSEU-create-oauth2-tokens-table.py b/migrations/auth/20221219_02_buSEU-create-oauth2-tokens-table.py
deleted file mode 100644
index 778282b..0000000
--- a/migrations/auth/20221219_02_buSEU-create-oauth2-tokens-table.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""
-create oauth2_tokens table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221219_01_CI3tN-create-oauth2-clients-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE oauth2_tokens(
-            token_id TEXT NOT NULL,
-            client_id TEXT NOT NULL,
-            token_type TEXT NOT NULL,
-            access_token TEXT UNIQUE NOT NULL,
-            refresh_token TEXT,
-            scope TEXT,
-            revoked INTEGER CHECK (revoked = 0 or revoked = 1),
-            issued_at INTEGER NOT NULL,
-            expires_in INTEGER NOT NULL,
-            user_id TEXT NOT NULL,
-            PRIMARY KEY(token_id),
-            FOREIGN KEY (client_id) REFERENCES oauth2_clients(client_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY (user_id) REFERENCES users(user_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS oauth2_tokens")
-]
diff --git a/migrations/auth/20221219_03_PcTrb-create-authorisation-code-table.py b/migrations/auth/20221219_03_PcTrb-create-authorisation-code-table.py
deleted file mode 100644
index 1683f87..0000000
--- a/migrations/auth/20221219_03_PcTrb-create-authorisation-code-table.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""
-create authorisation_code table
-"""
-
-from yoyo import step
-
-__depends__ = {'20221219_02_buSEU-create-oauth2-tokens-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE authorisation_code (
-            code_id TEXT NOT NULL,
-            code TEXT UNIQUE NOT NULL,
-            client_id NOT NULL,
-            redirect_uri TEXT,
-            scope TEXT,
-            nonce TEXT,
-            auth_time INTEGER NOT NULL,
-            code_challenge TEXT,
-            code_challenge_method TEXT,
-            user_id TEXT NOT NULL,
-            PRIMARY KEY (code_id),
-            FOREIGN KEY (client_id) REFERENCES oauth2_clients(client_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT,
-            FOREIGN KEY (user_id) REFERENCES users(user_id)
-              ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS authorisation_code")
-]
diff --git a/migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py b/migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py
deleted file mode 100644
index 7e7fda2..0000000
--- a/migrations/auth/20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""
-remove 'create-group' privilege from group-leader.
-"""
-
-from yoyo import step
-
-__depends__ = {'20221219_03_PcTrb-create-authorisation-code-table'}
-
-steps = [
-    step(
-        """
-        DELETE FROM role_privileges
-        WHERE role_id='a0e67630-d502-4b9f-b23f-6805d0f30e30'
-        AND privilege_id='4842e2aa-38b9-4349-805e-0a99a9cf8bff'
-        """,
-        """
-        INSERT INTO role_privileges VALUES
-        ('a0e67630-d502-4b9f-b23f-6805d0f30e30',
-        '4842e2aa-38b9-4349-805e-0a99a9cf8bff')
-        """),
-    step(
-        """
-        INSERT INTO roles(role_id, role_name, user_editable) VALUES
-          ('ade7e6b0-ba9c-4b51-87d0-2af7fe39a347', 'group-creator', '0')
-        """,
-        """
-        DELETE FROM roles WHERE role_id='ade7e6b0-ba9c-4b51-87d0-2af7fe39a347'
-        """),
-    step(
-        """
-        INSERT INTO role_privileges VALUES
-          ('ade7e6b0-ba9c-4b51-87d0-2af7fe39a347',
-           '4842e2aa-38b9-4349-805e-0a99a9cf8bff')
-        """,
-        """
-        DELETE FROM role_privileges
-        WHERE role_id='ade7e6b0-ba9c-4b51-87d0-2af7fe39a347'
-        AND privilege_id='4842e2aa-38b9-4349-805e-0a99a9cf8bff'
-        """)
-]
diff --git a/migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py b/migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py
deleted file mode 100644
index 1ef5ab0..0000000
--- a/migrations/auth/20230116_01_KwuJ3-rework-privileges-schema.py
+++ /dev/null
@@ -1,111 +0,0 @@
-"""
-rework privileges schema
-"""
-import contextlib
-
-from yoyo import step
-
-__depends__ = {'20230111_01_Wd6IZ-remove-create-group-privilege-from-group-leader'}
-
-privileges = ( # format: (original_id, original_name, new_id, category)
-    ("13ec2a94-4f1a-442d-aad2-936ad6dd5c57", "delete-group",
-     "system:group:delete-group", "group-management"),
-    ("1c59eff5-9336-4ed2-a166-8f70d4cb012e", "delete-role",
-     "group:role:delete-role", "role-management"),
-    ("1fe61370-cae9-4983-bd6c-ce61050c510f", "delete-any-user",
-     "system:user:delete-user", "user-management"),
-    ("221660b1-df05-4be1-b639-f010269dbda9", "create-role",
-     "group:role:create-role", "role-management"),
-    ("2f980855-959b-4339-b80e-25d1ec286e21", "edit-resource",
-     "group:resource:edit-resource", "resource-management"),
-    ("3ebfe79c-d159-4629-8b38-772cf4bc2261", "view-group",
-     "system:group:view-group", "group-management"),
-    ("4842e2aa-38b9-4349-805e-0a99a9cf8bff", "create-group",
-     "system:group:create-group", "group-management"),
-    ("5103cc68-96f8-4ebb-83a4-a31692402c9b", "assign-role",
-     "group:user:assign-role", "role-management"),
-    ("519db546-d44e-4fdc-9e4e-25aa67548ab3", "masquerade",
-     "system:user:masquerade", "system-admin"),
-    ("52576370-b3c7-4e6a-9f7e-90e9dbe24d8f", "edit-group",
-     "system:group:edit-group", "group-management"),
-    ("7bcca363-cba9-4169-9e31-26bdc6179b28", "edit-role",
-     "group:role:edit-role", "role-management"),
-    ("7f261757-3211-4f28-a43f-a09b800b164d", "view-resource",
-     "group:resource:view-resource", "resource-management"),
-    ("80f11285-5079-4ec0-907c-06509f88a364", "assign-group-leader",
-     "system:user:assign-group-leader", "group-management"),
-    ("aa25b32a-bff2-418d-b0a2-e26b4a8f089b", "create-resource",
-     "group:resource:create-resource", "resource-management"),
-    ("ae4add8c-789a-4d11-a6e9-a306470d83d9", "add-group-member",
-     "group:user:add-group-member", "group-management"),
-    ("d2a070fd-e031-42fb-ba41-d60cf19e5d6d", "delete-resource",
-     "group:resource:delete-resource", "resource-management"),
-    ("d4afe2b3-4ca0-4edd-b37d-966535b5e5bd", "transfer-group-leadership",
-     "system:group:transfer-group-leader", "group-management"),
-    ("e7252301-6ee0-43ba-93ef-73b607cf06f6", "reset-any-password",
-     "system:user:reset-password", "user-management"),
-    ("f1bd3f42-567e-4965-9643-6d1a52ddee64", "remove-group-member",
-     "group:user:remove-group-member", "group-management"))
-
-def rework_privileges_table(cursor):
-    "rework the schema"
-    cursor.executemany(
-        ("UPDATE privileges SET privilege_id=:id "
-         "WHERE privilege_id=:old_id"),
-        ({"id": row[2], "old_id": row[0]} for row in privileges))
-    cursor.execute("ALTER TABLE privileges DROP COLUMN privilege_category")
-    cursor.execute("ALTER TABLE privileges DROP COLUMN privilege_name")
-
-def restore_privileges_table(cursor):
-    "restore the schema"
-    cursor.execute((
-        "CREATE TABLE privileges_restore ("
-        "  privilege_id TEXT PRIMARY KEY,"
-        "  privilege_name TEXT NOT NULL,"
-        "  privilege_category TEXT NOT NULL DEFAULT 'common',"
-        "  privilege_description TEXT"
-        ")"))
-    id_dict = {row[2]: {"id": row[0], "name": row[1], "cat": row[3]}
-               for row in privileges}
-    cursor.execute(
-        "SELECT privilege_id, privilege_description FROM privileges")
-    params = ({**id_dict[row[0]], "desc": row[1]} for row in cursor.fetchall())
-    cursor.executemany(
-        "INSERT INTO privileges_restore VALUES (:id, :name, :cat, :desc)",
-        params)
-    cursor.execute("DROP TABLE privileges")
-    cursor.execute("ALTER TABLE privileges_restore RENAME TO privileges")
-
-def update_privilege_ids_in_role_privileges(cursor):
-    """Update the ids to new form."""
-    cursor.executemany(
-        ("UPDATE role_privileges SET privilege_id=:new_id "
-         "WHERE privilege_id=:old_id"),
-        ({"new_id": row[2], "old_id": row[0]} for row in privileges))
-
-def restore_privilege_ids_in_role_privileges(cursor):
-    """Restore original ids"""
-    cursor.executemany(
-        ("UPDATE role_privileges SET privilege_id=:old_id "
-         "WHERE privilege_id=:new_id"),
-        ({"new_id": row[2], "old_id": row[0]} for row in privileges))
-
-def change_schema(conn):
-    """Change the privileges schema and IDs"""
-    with contextlib.closing(conn.cursor()) as cursor:
-        cursor.execute("PRAGMA foreign_keys=OFF")
-        rework_privileges_table(cursor)
-        update_privilege_ids_in_role_privileges(cursor)
-        cursor.execute("PRAGMA foreign_keys=ON")
-
-def restore_schema(conn):
-    """Change the privileges schema and IDs"""
-    with contextlib.closing(conn.cursor()) as cursor:
-        cursor.execute("PRAGMA foreign_keys=OFF")
-        restore_privilege_ids_in_role_privileges(cursor)
-        restore_privileges_table(cursor)
-        cursor.execute("PRAGMA foreign_keys=ON")
-
-steps = [
-    step(change_schema, restore_schema)
-]
diff --git a/migrations/auth/20230207_01_r0bkZ-create-group-join-requests-table.py b/migrations/auth/20230207_01_r0bkZ-create-group-join-requests-table.py
deleted file mode 100644
index ceae5ea..0000000
--- a/migrations/auth/20230207_01_r0bkZ-create-group-join-requests-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create group_requests table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230116_01_KwuJ3-rework-privileges-schema'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS group_join_requests(
-            request_id TEXT NOT NULL,
-            group_id TEXT NOT NULL,
-            requester_id TEXT NOT NULL,
-            timestamp REAL NOT NULL,
-            status TEXT NOT NULL DEFAULT 'PENDING',
-            message TEXT,
-            PRIMARY KEY(request_id, group_id),
-            FOREIGN KEY(group_id) REFERENCES groups(group_id)
-            ON UPDATE CASCADE ON DELETE CASCADE,
-            FOREIGN KEY (requester_id) REFERENCES users(user_id)
-            ON UPDATE CASCADE ON DELETE CASCADE,
-            UNIQUE(group_id, requester_id),
-            CHECK (status IN ('PENDING', 'ACCEPTED', 'REJECTED'))
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS group_join_requests")
-]
diff --git a/migrations/auth/20230210_01_8xMa1-system-admin-privileges-for-data-distribution.py b/migrations/auth/20230210_01_8xMa1-system-admin-privileges-for-data-distribution.py
deleted file mode 100644
index 8b406a6..0000000
--- a/migrations/auth/20230210_01_8xMa1-system-admin-privileges-for-data-distribution.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-System admin privileges for data distribution
-
-These privileges are focussed on allowing the system administrator to link the
-datasets and traits in the main database to specific groups in the auth system.
-"""
-
-from yoyo import step
-
-__depends__ = {'20230207_01_r0bkZ-create-group-join-requests-table'}
-
-steps = [
-    step(
-        """
-        INSERT INTO privileges VALUES
-          ('system:data:link-to-group', 'Link a dataset or trait to a group.')
-        """,
-        """
-        DELETE FROM privileges WHERE privilege_id IN
-         ('system:data:link-to-group')
-        """)
-]
diff --git a/migrations/auth/20230210_02_lDK14-create-system-admin-role.py b/migrations/auth/20230210_02_lDK14-create-system-admin-role.py
deleted file mode 100644
index 9b3fc2b..0000000
--- a/migrations/auth/20230210_02_lDK14-create-system-admin-role.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
-Create system-admin role
-"""
-import uuid
-from contextlib import closing
-
-from yoyo import step
-
-__depends__ = {'20230210_01_8xMa1-system-admin-privileges-for-data-distribution'}
-
-def create_sys_admin_role(conn):
-    with closing(conn.cursor()) as cursor:
-        role_id = uuid.uuid4()
-        cursor.execute(
-            "INSERT INTO roles VALUES (?, 'system-administrator', '0')",
-            (str(role_id),))
-
-        cursor.executemany(
-            "INSERT INTO role_privileges VALUES (:role_id, :privilege_id)",
-            ({"role_id": f"{role_id}", "privilege_id": priv}
-         for priv in (
-                 "system:data:link-to-group",
-                 "system:group:create-group",
-                 "system:group:delete-group",
-                 "system:group:edit-group",
-                 "system:group:transfer-group-leader",
-                 "system:group:view-group",
-                 "system:user:assign-group-leader",
-                 "system:user:delete-user",
-                 "system:user:masquerade",
-                 "system:user:reset-password")))
-
-def drop_sys_admin_role(conn):
-    pass
-
-steps = [
-    step(create_sys_admin_role, drop_sys_admin_role)
-]
diff --git a/migrations/auth/20230306_01_pRfxl-add-system-user-list-privilege.py b/migrations/auth/20230306_01_pRfxl-add-system-user-list-privilege.py
deleted file mode 100644
index 84bbd49..0000000
--- a/migrations/auth/20230306_01_pRfxl-add-system-user-list-privilege.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""
-Add system:user:list privilege
-"""
-import contextlib
-
-from yoyo import step
-
-__depends__ = {'20230210_02_lDK14-create-system-admin-role'}
-
-def insert_users_list_priv(conn):
-    """Create a new 'system:user:list' privilege."""
-    with contextlib.closing(conn.cursor()) as cursor:
-        cursor.execute(
-            "INSERT INTO privileges(privilege_id, privilege_description) "
-            "VALUES('system:user:list', 'List users in the system') "
-            "ON CONFLICT (privilege_id) DO NOTHING")
-
-def delete_users_list_priv(conn):
-    """Delete the new 'system:user:list' privilege."""
-    with contextlib.closing(conn.cursor()) as cursor:
-        cursor.execute(
-            "DELETE FROM privileges WHERE privilege_id='system:user:list'")
-
-steps = [
-    step(insert_users_list_priv, delete_users_list_priv)
-]
diff --git a/migrations/auth/20230306_02_7GnRY-add-system-user-list-privilege-to-system-administrator-and-group-leader-roles.py b/migrations/auth/20230306_02_7GnRY-add-system-user-list-privilege-to-system-administrator-and-group-leader-roles.py
deleted file mode 100644
index 3caad55..0000000
--- a/migrations/auth/20230306_02_7GnRY-add-system-user-list-privilege-to-system-administrator-and-group-leader-roles.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""
-Add system:user:list privilege to system-administrator and group-leader roles.
-"""
-import uuid
-import contextlib
-
-from yoyo import step
-
-__depends__ = {'20230306_01_pRfxl-add-system-user-list-privilege'}
-
-def role_ids(cursor):
-    """Get role ids from names"""
-    cursor.execute(
-        "SELECT * FROM roles WHERE role_name IN "
-        "('system-administrator', 'group-leader')")
-    return (uuid.UUID(row[0]) for row in cursor.fetchall())
-
-def add_privilege_to_roles(conn):
-    """
-    Add 'system:user:list' privilege to 'system-administrator' and
-    'group-leader' roles."""
-    with contextlib.closing(conn.cursor()) as cursor:
-        cursor.executemany(
-            "INSERT INTO role_privileges(role_id,privilege_id) "
-            "VALUES(?, ?)",
-            tuple((str(role_id), "system:user:list")
-                  for role_id in role_ids(cursor)))
-
-def del_privilege_from_roles(conn):
-    """
-    Delete 'system:user:list' privilege to 'system-administrator' and
-    'group-leader' roles.
-    """
-    with contextlib.closing(conn.cursor()) as cursor:
-        cursor.execute(
-            "DELETE FROM role_privileges WHERE "
-            "role_id IN (?, ?) AND privilege_id='system:user:list'",
-            tuple(str(role_id) for role_id in role_ids(cursor)))
-
-steps = [
-    step(add_privilege_to_roles, del_privilege_from_roles)
-]
diff --git a/migrations/auth/20230322_01_0dDZR-create-linked-phenotype-data-table.py b/migrations/auth/20230322_01_0dDZR-create-linked-phenotype-data-table.py
deleted file mode 100644
index 647325f..0000000
--- a/migrations/auth/20230322_01_0dDZR-create-linked-phenotype-data-table.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
-Create linked-phenotype-data table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230306_02_7GnRY-add-system-user-list-privilege-to-system-administrator-and-group-leader-roles'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS linked_phenotype_data
-        -- Link the data in MariaDB to user groups in the auth system
-        (
-          data_link_id TEXT NOT NULL PRIMARY KEY, -- A new ID for the auth system
-          group_id TEXT NOT NULL, -- The user group the data is linked to
-          SpeciesId TEXT NOT NULL, -- The species in MariaDB
-          InbredSetId TEXT NOT NULL, -- The traits group in MariaDB
-          PublishFreezeId TEXT NOT NULL, -- The dataset Id in MariaDB
-          dataset_name TEXT, -- dataset Name in MariaDB
-          dataset_fullname, -- dataset FullName in MariaDB
-          dataset_shortname, -- dataset ShortName in MariaDB
-          PublishXRefId TEXT NOT NULL, -- The trait's ID in MariaDB
-          FOREIGN KEY (group_id)
-            REFERENCES groups(group_id) ON UPDATE CASCADE ON DELETE RESTRICT
-          UNIQUE (SpeciesId, InbredSetId, PublishFreezeId, PublishXRefId)
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS linked_phenotype_data")
-]
diff --git a/migrations/auth/20230322_02_Ll854-create-phenotype-resources-table.py b/migrations/auth/20230322_02_Ll854-create-phenotype-resources-table.py
deleted file mode 100644
index 7c9e986..0000000
--- a/migrations/auth/20230322_02_Ll854-create-phenotype-resources-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create phenotype_resources table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230322_01_0dDZR-create-linked-phenotype-data-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS phenotype_resources
-        -- Link phenotype data to specific resources
-        (
-          group_id TEXT NOT NULL,
-          resource_id TEXT NOT NULL, -- A resource can have multiple data items
-          data_link_id TEXT NOT NULL,
-          PRIMARY KEY(group_id, resource_id, data_link_id),
-          UNIQUE (data_link_id), -- ensure data is linked to only one resource
-          FOREIGN KEY (group_id, resource_id)
-            REFERENCES resources(group_id, resource_id)
-            ON UPDATE CASCADE ON DELETE RESTRICT,
-          FOREIGN KEY (data_link_id)
-            REFERENCES linked_phenotype_data(data_link_id)
-            ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS phenotype_resources")
-]
diff --git a/migrations/auth/20230404_01_VKxXg-create-linked-genotype-data-table.py b/migrations/auth/20230404_01_VKxXg-create-linked-genotype-data-table.py
deleted file mode 100644
index 02e8718..0000000
--- a/migrations/auth/20230404_01_VKxXg-create-linked-genotype-data-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create linked genotype data table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230322_02_Ll854-create-phenotype-resources-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS linked_genotype_data
-        -- Link genotype data in MariaDB to user groups in auth system
-        (
-          data_link_id TEXT NOT NULL PRIMARY KEY, -- A new ID for the auth system
-          group_id TEXT NOT NULL, -- The user group the data is linked to
-          SpeciesId TEXT NOT NULL, -- The species in MariaDB
-          InbredSetId TEXT NOT NULL, -- The traits group in MariaDB
-          GenoFreezeId TEXT NOT NULL, -- The dataset Id in MariaDB
-          dataset_name TEXT, -- dataset Name in MariaDB
-          dataset_fullname, -- dataset FullName in MariaDB
-          dataset_shortname, -- dataset ShortName in MariaDB
-          FOREIGN KEY (group_id)
-            REFERENCES groups(group_id) ON UPDATE CASCADE ON DELETE RESTRICT
-          UNIQUE (SpeciesId, InbredSetId, GenoFreezeId)
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS linked_genotype_data")
-]
diff --git a/migrations/auth/20230404_02_la33P-create-genotype-resources-table.py b/migrations/auth/20230404_02_la33P-create-genotype-resources-table.py
deleted file mode 100644
index 1a865e0..0000000
--- a/migrations/auth/20230404_02_la33P-create-genotype-resources-table.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""
-Create genotype resources table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230404_01_VKxXg-create-linked-genotype-data-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS genotype_resources
-        -- Link genotype data to specific resource
-        (
-          group_id TEXT NOT NULL,
-          resource_id TEXT NOT NULL, -- A resource can have multiple items
-          data_link_id TEXT NOT NULL,
-          PRIMARY KEY (group_id, resource_id, data_link_id),
-          UNIQUE (data_link_id) -- ensure data is linked to single resource
-          FOREIGN KEY (group_id, resource_id)
-            REFERENCES resources(group_id, resource_id)
-            ON UPDATE CASCADE ON DELETE RESTRICT,
-          FOREIGN KEY (data_link_id)
-            REFERENCES linked_genotype_data(data_link_id)
-            ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS genotype_resources")
-]
diff --git a/migrations/auth/20230410_01_8mwaf-create-linked-mrna-data-table.py b/migrations/auth/20230410_01_8mwaf-create-linked-mrna-data-table.py
deleted file mode 100644
index db9a6bf..0000000
--- a/migrations/auth/20230410_01_8mwaf-create-linked-mrna-data-table.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
-Create linked mrna data table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230404_02_la33P-create-genotype-resources-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS linked_mrna_data
-        -- Link mRNA Assay data in MariaDB to user groups in auth system
-        (
-          data_link_id TEXT NOT NULL PRIMARY KEY, -- A new ID for the auth system
-          group_id TEXT NOT NULL, -- The user group the data is linked to
-          SpeciesId TEXT NOT NULL, -- The species in MariaDB
-          InbredSetId TEXT NOT NULL, -- The traits group in MariaDB
-          ProbeFreezeId TEXT NOT NULL, -- The study ID in MariaDB
-          ProbeSetFreezeId TEXT NOT NULL, -- The dataset Id in MariaDB
-          dataset_name TEXT, -- dataset Name in MariaDB
-          dataset_fullname, -- dataset FullName in MariaDB
-          dataset_shortname, -- dataset ShortName in MariaDB
-          FOREIGN KEY (group_id)
-            REFERENCES groups(group_id) ON UPDATE CASCADE ON DELETE RESTRICT
-          UNIQUE (SpeciesId, InbredSetId, ProbeFreezeId, ProbeSetFreezeId)
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS linked_mrna_data")
-]
diff --git a/migrations/auth/20230410_02_WZqSf-create-mrna-resources-table.py b/migrations/auth/20230410_02_WZqSf-create-mrna-resources-table.py
deleted file mode 100644
index 2ad1056..0000000
--- a/migrations/auth/20230410_02_WZqSf-create-mrna-resources-table.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""
-Create mRNA resources table
-"""
-
-from yoyo import step
-
-__depends__ = {'20230410_01_8mwaf-create-linked-mrna-data-table'}
-
-steps = [
-    step(
-        """
-        CREATE TABLE IF NOT EXISTS mrna_resources
-        -- Link mRNA data to specific resource
-        (
-          group_id TEXT NOT NULL,
-          resource_id TEXT NOT NULL, -- A resource can have multiple items
-          data_link_id TEXT NOT NULL,
-          PRIMARY KEY (resource_id, data_link_id),
-          UNIQUE (data_link_id) -- ensure data is linked to single resource
-          FOREIGN KEY (group_id, resource_id)
-            REFERENCES resources(group_id, resource_id)
-            ON UPDATE CASCADE ON DELETE RESTRICT,
-          FOREIGN KEY (data_link_id) REFERENCES linked_mrna_data(data_link_id)
-            ON UPDATE CASCADE ON DELETE RESTRICT
-        ) WITHOUT ROWID
-        """,
-        "DROP TABLE IF EXISTS mrna_resources")
-]
diff --git a/scripts/migrate_existing_data.py b/scripts/migrate_existing_data.py
deleted file mode 100644
index 186f7f8..0000000
--- a/scripts/migrate_existing_data.py
+++ /dev/null
@@ -1,381 +0,0 @@
-"""
-Migrate existing data that is not assigned to any group to the default sys-admin
-group for accessibility purposes.
-"""
-import sys
-import json
-import time
-import random
-from pathlib import Path
-from uuid import UUID, uuid4
-
-import click
-from MySQLdb.cursors import DictCursor
-
-from gn3 import db_utils as biodb
-
-from gn3.auth import db as authdb
-from gn3.auth.authentication.users import User
-from gn3.auth.authorisation.groups.models import Group, save_group
-from gn3.auth.authorisation.roles.models import (
-    revoke_user_role_by_name, assign_user_role_by_name)
-from gn3.auth.authorisation.resources.models import (
-    Resource, ResourceCategory, __assign_resource_owner_role__)
-
-class DataNotFound(Exception):
-    """Raise if no admin user exists."""
-
-def sys_admins(conn: authdb.DbConnection) -> tuple[User, ...]:
-    """Retrieve all the existing system admins."""
-    with authdb.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT u.* FROM users AS u "
-            "INNER JOIN user_roles AS ur ON u.user_id=ur.user_id "
-            "INNER JOIN roles AS r ON ur.role_id=r.role_id "
-            "WHERE r.role_name='system-administrator'")
-        return tuple(User(UUID(row["user_id"]), row["email"], row["name"])
-                     for row in cursor.fetchall())
-    return tuple()
-
-def choose_admin(enum_admins: dict[int, User]) -> int:
-    """Prompt and read user choice."""
-    while True:
-        try:
-            print("\n===========================\n")
-            print("We found the following system administrators:")
-            for idx, admin in enum_admins.items():
-                print(f"\t{idx}: {admin.name} ({admin.email})")
-            choice = input(f"Choose [1 .. {len(enum_admins)}]: ")
-            return int(choice)
-        except ValueError as _verr:
-            if choice.lower() == "quit":
-                print("Goodbye!")
-                sys.exit(0)
-            print(f"\nERROR: Invalid choice '{choice}'!")
-
-def select_sys_admin(admins: tuple[User, ...]) -> User:
-    """Pick one admin out of list."""
-    if len(admins) > 0:
-        if len(admins) == 1:
-            print(f"-> Found Admin: {admins[0].name} ({admins[0].email})")
-            return admins[0]
-        enum_admins = dict(enumerate(admins, start=1))
-        chosen = enum_admins[choose_admin(enum_admins)]
-        print(f"-> Chosen Admin: {chosen.name} ({chosen.email})")
-        return chosen
-    raise DataNotFound(
-        "No administrator user found. Create an administrator user first.")
-
-def admin_group(conn: authdb.DbConnection, admin: User) -> Group:
-    """Retrieve the admin's user group. If none exist, create one."""
-    with authdb.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT g.* FROM users AS u "
-            "INNER JOIN group_users AS gu ON u.user_id=gu.user_id "
-            "INNER JOIN groups AS g on gu.group_id=g.group_id "
-            "WHERE u.user_id = ?",
-            (str(admin.user_id),))
-        row = cursor.fetchone()
-        if row:
-            return Group(UUID(row["group_id"]),
-                         row["group_name"],
-                         json.loads(row["group_metadata"]))
-        new_group = save_group(cursor, "AutoAdminGroup", {
-            "group_description": (
-                "Created by script for existing data visibility. "
-                "Existing data was migrated into this group and assigned "
-                "to publicly visible resources according to type.")
-        })
-        cursor.execute("INSERT INTO group_users VALUES (?, ?)",
-                       (str(new_group.group_id), str(admin.user_id)))
-        revoke_user_role_by_name(cursor, admin, "group-creator")
-        assign_user_role_by_name(cursor, admin, "group-leader")
-        return new_group
-
-def __resource_category_by_key__(
-        cursor: authdb.DbCursor, category_key: str) -> ResourceCategory:
-    """Retrieve a resource category by its ID."""
-    cursor.execute(
-        "SELECT * FROM resource_categories WHERE resource_category_key = ?",
-        (category_key,))
-    row = cursor.fetchone()
-    if not bool(row):
-        raise DataNotFound(
-            f"Could not find resource category with key {category_key}")
-    return ResourceCategory(UUID(row["resource_category_id"]),
-                            row["resource_category_key"],
-                            row["resource_category_description"])
-
-def __create_resources__(cursor: authdb.DbCursor, group: Group) -> tuple[
-        Resource, ...]:
-    """Create default resources."""
-    resources = tuple(Resource(
-        group, uuid4(), name, __resource_category_by_key__(cursor, catkey),
-        True, tuple()
-    ) for name, catkey in (
-        ("mRNA-euhrin", "mrna"),
-        ("pheno-xboecp", "phenotype"),
-        ("geno-welphd", "genotype")))
-    cursor.executemany(
-        "INSERT INTO resources VALUES (:gid, :rid, :rname, :rcid, :pub)",
-        tuple({
-            "gid": str(group.group_id),
-            "rid": str(res.resource_id),
-            "rname": res.resource_name,
-            "rcid": str(res.resource_category.resource_category_id),
-            "pub": 1
-        } for res in resources))
-    return resources
-
-def default_resources(conn: authdb.DbConnection, group: Group) -> tuple[
-        Resource, ...]:
-    """Create default resources, or return them if they exist."""
-    with authdb.cursor(conn) as cursor:
-        cursor.execute(
-            "SELECT r.resource_id, r.resource_name, r.public, rc.* "
-            "FROM resources AS r INNER JOIN resource_categories AS rc "
-            "ON r.resource_category_id=rc.resource_category_id "
-            "WHERE r.group_id=? AND r.resource_name IN "
-            "('mRNA-euhrin', 'pheno-xboecp', 'geno-welphd')",
-            (str(group.group_id),))
-        rows = cursor.fetchall()
-        if len(rows) == 0:
-            return __create_resources__(cursor, group)
-
-        return tuple(Resource(
-            group,
-            UUID(row["resource_id"]),
-            row["resource_name"],
-            ResourceCategory(
-                UUID(row["resource_category_id"]),
-                row["resource_category_key"],
-                row["resource_category_description"]),
-            bool(row["public"]),
-            tuple()
-        ) for row in rows)
-
-def delay():
-    """Delay a while: anything from 2 seconds to 15 seconds."""
-    time.sleep(random.choice(range(2,16)))
-
-def __assigned_mrna__(authconn):
-    """Retrieve assigned mRNA items."""
-    with authdb.cursor(authconn) as cursor:
-        cursor.execute(
-            "SELECT SpeciesId, InbredSetId, ProbeFreezeId, ProbeSetFreezeId "
-            "FROM linked_mrna_data")
-        return tuple(
-            (row["SpeciesId"], row["InbredSetId"], row["ProbeFreezeId"],
-             row["ProbeSetFreezeId"]) for row in cursor.fetchall())
-
-def __unassigned_mrna__(bioconn, assigned):
-    """Retrieve unassigned mRNA data items."""
-    query = (
-        "SELECT s.SpeciesId, iset.InbredSetId, pf.ProbeFreezeId, "
-        "psf.Id AS ProbeSetFreezeId, psf.Name AS dataset_name, "
-        "psf.FullName AS dataset_fullname, psf.ShortName AS dataset_shortname "
-        "FROM Species AS s INNER JOIN InbredSet AS iset "
-        "ON s.SpeciesId=iset.SpeciesId INNER JOIN ProbeFreeze AS pf "
-        "ON iset.InbredSetId=pf.InbredSetId INNER JOIN ProbeSetFreeze AS psf "
-        "ON pf.ProbeFreezeId=psf.ProbeFreezeId ")
-    if len(assigned) > 0:
-        paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(assigned))
-        query = query + (
-            "WHERE (s.SpeciesId, iset.InbredSetId, pf.ProbeFreezeId, psf.Id) "
-            f"NOT IN ({paramstr}) ")
-
-    query = query + "LIMIT 100000"
-    with bioconn.cursor(DictCursor) as cursor:
-        cursor.execute(query, tuple(item for row in assigned for item in row))
-        return (row for row in cursor.fetchall())
-
-def __assign_mrna__(authconn, bioconn, resource):
-    "Assign any unassigned mRNA data to resource."
-    while True:
-        unassigned = tuple({
-            "data_link_id": str(uuid4()),
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            **row
-        } for row in __unassigned_mrna__(
-            bioconn, __assigned_mrna__(authconn)))
-
-        if len(unassigned) <= 0:
-            print("-> mRNA: Completed!")
-            break
-        with authdb.cursor(authconn) as cursor:
-            cursor.executemany(
-                "INSERT INTO linked_mrna_data VALUES "
-                "(:data_link_id, :group_id, :SpeciesId, :InbredSetId, "
-                ":ProbeFreezeId, :ProbeSetFreezeId, :dataset_name, "
-                ":dataset_fullname, :dataset_shortname)",
-                unassigned)
-            cursor.executemany(
-                "INSERT INTO mrna_resources VALUES "
-                "(:group_id, :resource_id, :data_link_id)",
-                unassigned)
-            print(f"-> mRNA: Linked {len(unassigned)}")
-            delay()
-
-def __assigned_geno__(authconn):
-    """Retrieve assigned genotype data."""
-    with authdb.cursor(authconn) as cursor:
-        cursor.execute(
-            "SELECT SpeciesId, InbredSetId, GenoFreezeId "
-            "FROM linked_genotype_data")
-        return tuple((row["SpeciesId"], row["InbredSetId"], row["GenoFreezeId"])
-                     for row in cursor.fetchall())
-
-def __unassigned_geno__(bioconn, assigned):
-    """Fetch unassigned genotype data."""
-    query = (
-        "SELECT s.SpeciesId, iset.InbredSetId, iset.InbredSetName, "
-        "gf.Id AS GenoFreezeId, gf.Name AS dataset_name, "
-        "gf.FullName AS dataset_fullname, "
-        "gf.ShortName AS dataset_shortname "
-        "FROM Species AS s INNER JOIN InbredSet AS iset "
-        "ON s.SpeciesId=iset.SpeciesId INNER JOIN GenoFreeze AS gf "
-        "ON iset.InbredSetId=gf.InbredSetId ")
-    if len(assigned) > 0:
-        paramstr = ", ".join(["(%s, %s, %s)"] * len(assigned))
-        query = query + (
-            "WHERE (s.SpeciesId, iset.InbredSetId, gf.Id) "
-            f"NOT IN ({paramstr}) ")
-
-    query = query + "LIMIT 100000"
-    with bioconn.cursor(DictCursor) as cursor:
-        cursor.execute(query, tuple(item for row in assigned for item in row))
-        return (row for row in cursor.fetchall())
-
-def __assign_geno__(authconn, bioconn, resource):
-    "Assign any unassigned Genotype data to resource."
-    while True:
-        unassigned = tuple({
-            "data_link_id": str(uuid4()),
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            **row
-        } for row in __unassigned_geno__(
-            bioconn, __assigned_geno__(authconn)))
-
-        if len(unassigned) <= 0:
-            print("-> Genotype: Completed!")
-            break
-        with authdb.cursor(authconn) as cursor:
-            cursor.executemany(
-                "INSERT INTO linked_genotype_data VALUES "
-                "(:data_link_id, :group_id, :SpeciesId, :InbredSetId, "
-                ":GenoFreezeId, :dataset_name, :dataset_fullname, "
-                ":dataset_shortname)",
-                unassigned)
-            cursor.executemany(
-                "INSERT INTO genotype_resources VALUES "
-                "(:group_id, :resource_id, :data_link_id)",
-                unassigned)
-            print(f"-> Genotype: Linked {len(unassigned)}")
-            delay()
-
-def __assigned_pheno__(authconn):
-    """Retrieve assigned phenotype data."""
-    with authdb.cursor(authconn) as cursor:
-        cursor.execute(
-            "SELECT SpeciesId, InbredSetId, PublishFreezeId, PublishXRefId "
-            "FROM linked_phenotype_data")
-        return tuple((
-            row["SpeciesId"], row["InbredSetId"], row["PublishFreezeId"],
-            row["PublishXRefId"]) for row in cursor.fetchall())
-
-def __unassigned_pheno__(bioconn, assigned):
-    """Retrieve all unassigned Phenotype data."""
-    query = (
-            "SELECT spc.SpeciesId, iset.InbredSetId, "
-            "pf.Id AS PublishFreezeId, pf.Name AS dataset_name, "
-            "pf.FullName AS dataset_fullname, "
-            "pf.ShortName AS dataset_shortname, pxr.Id AS PublishXRefId "
-            "FROM "
-            "Species AS spc "
-            "INNER JOIN InbredSet AS iset "
-            "ON spc.SpeciesId=iset.SpeciesId "
-            "INNER JOIN PublishFreeze AS pf "
-            "ON iset.InbredSetId=pf.InbredSetId "
-            "INNER JOIN PublishXRef AS pxr "
-            "ON pf.InbredSetId=pxr.InbredSetId ")
-    if len(assigned) > 0:
-        paramstr = ", ".join(["(%s, %s, %s, %s)"] * len(assigned))
-        query = query + (
-            "WHERE (spc.SpeciesId, iset.InbredSetId, pf.Id, pxr.Id) "
-            f"NOT IN ({paramstr}) ")
-
-    query = query + "LIMIT 100000"
-    with bioconn.cursor(DictCursor) as cursor:
-        cursor.execute(query, tuple(item for row in assigned for item in row))
-        return (row for row in cursor.fetchall())
-
-def __assign_pheno__(authconn, bioconn, resource):
-    """Assign any unassigned Phenotype data to resource."""
-    while True:
-        unassigned = tuple({
-            "data_link_id": str(uuid4()),
-            "group_id": str(resource.group.group_id),
-            "resource_id": str(resource.resource_id),
-            **row
-        } for row in __unassigned_pheno__(
-            bioconn, __assigned_pheno__(authconn)))
-
-        if len(unassigned) <= 0:
-            print("-> Phenotype: Completed!")
-            break
-        with authdb.cursor(authconn) as cursor:
-            cursor.executemany(
-                "INSERT INTO linked_phenotype_data VALUES "
-                "(:data_link_id, :group_id, :SpeciesId, :InbredSetId, "
-                ":PublishFreezeId, :dataset_name, :dataset_fullname, "
-                ":dataset_shortname, :PublishXRefId)",
-                unassigned)
-            cursor.executemany(
-                "INSERT INTO phenotype_resources VALUES "
-                "(:group_id, :resource_id, :data_link_id)",
-                unassigned)
-            print(f"-> Phenotype: Linked {len(unassigned)}")
-            delay()
-
-def assign_data_to_resource(authconn, bioconn, resource: Resource):
-    """Assign existing data, not linked to any group to the resource."""
-    assigner_fns = {
-        "mrna": __assign_mrna__,
-        "genotype": __assign_geno__,
-        "phenotype": __assign_pheno__
-    }
-    return assigner_fns[resource.resource_category.resource_category_key](
-        authconn, bioconn, resource)
-
-def entry(authdbpath, mysqldburi):
-    """Entry-point for data migration."""
-    if not Path(authdbpath).exists():
-        print(
-            f"ERROR: Auth db file `{authdbpath}` does not exist.",
-            file=sys.stderr)
-        sys.exit(2)
-    try:
-        with (authdb.connection(authdbpath) as authconn,
-              biodb.database_connection(mysqldburi) as bioconn):
-            admin = select_sys_admin(sys_admins(authconn))
-            resources = default_resources(
-                authconn, admin_group(authconn, admin))
-            for resource in resources:
-                assign_data_to_resource(authconn, bioconn, resource)
-                with authdb.cursor(authconn) as cursor:
-                    __assign_resource_owner_role__(cursor, resource, admin)
-    except DataNotFound as dnf:
-        print(dnf.args[0], file=sys.stderr)
-        sys.exit(1)
-
-@click.command()
-@click.argument("authdbpath") # "Path to the Auth(entic|oris)ation database"
-@click.argument("mysqldburi") # "URI to the MySQL database with the biology data"
-def run(authdbpath, mysqldburi):
-    """Setup command-line arguments."""
-    entry(authdbpath, mysqldburi)
-
-if __name__ == "__main__":
-    run() # pylint: disable=[no-value-for-parameter]
diff --git a/scripts/register_sys_admin.py b/scripts/register_sys_admin.py
deleted file mode 100644
index 1696adb..0000000
--- a/scripts/register_sys_admin.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""Script to register and mark a user account as sysadmin."""
-import sys
-import uuid
-import getpass
-from pathlib import Path
-
-import click
-from email_validator import validate_email, EmailNotValidError
-
-from gn3.auth import db
-from gn3.auth.authentication.users import hash_password
-
-def fetch_email() -> str:
-    """Prompt user for email."""
-    while True:
-        try:
-            user_input = input("Enter the administrator's email: ")
-            email = validate_email(user_input.strip(), check_deliverability=True)
-            return email["email"]
-        except EmailNotValidError as _enve:
-            print("You did not provide a valid email address. Try again...",
-                  file=sys.stderr)
-
-def fetch_password() -> str:
-    """Prompt user for password."""
-    while True:
-        passwd = getpass.getpass(prompt="Enter password: ").strip()
-        passwd2 = getpass.getpass(prompt="Confirm password: ").strip()
-        if passwd != "" and passwd == passwd2:
-            return passwd
-        if passwd == "":
-            print("Empty password not accepted", file=sys.stderr)
-            continue
-        if passwd != passwd2:
-            print("Passwords *MUST* match", file=sys.stderr)
-            continue
-
-def fetch_name() -> str:
-    """Prompt user for name"""
-    while True:
-        name = input("Enter the user's name: ").strip()
-        if name == "":
-            print("Invalid name.")
-            continue
-        return name
-
-def save_admin(conn: db.DbConnection, name: str, email: str, passwd: str):
-    """Save the details to the database and assign the new user as admin."""
-    admin_id = uuid.uuid4()
-    admin = {
-        "user_id": str(admin_id),
-        "email": email,
-        "name": name,
-        "hash": hash_password(passwd)
-    }
-    with db.cursor(conn) as cursor:
-        cursor.execute("INSERT INTO users VALUES (:user_id, :email, :name)",
-                       admin)
-        cursor.execute("INSERT INTO user_credentials VALUES (:user_id, :hash)",
-                       admin)
-        cursor.execute(
-            "SELECT * FROM roles WHERE role_name='system-administrator'")
-        admin_role = cursor.fetchone()
-        cursor.execute("INSERT INTO user_roles VALUES (:user_id, :role_id)",
-                       {**admin, "role_id": admin_role["role_id"]})
-        return 0
-
-def register_admin(authdbpath: Path):
-    """Register a user as a system admin."""
-    assert authdbpath.exists(), "Could not find database file."
-    with db.connection(str(authdbpath)) as conn:
-        return save_admin(conn, fetch_name(), fetch_email(), fetch_password())
-
-if __name__ == "__main__":
-    @click.command()
-    @click.argument("authdbpath")
-    def run(authdbpath):
-        """Entry-point for when script is run directly"""
-        return register_admin(Path(authdbpath).absolute())
-
-    run()# pylint: disable=[no-value-for-parameter]
diff --git a/tests/unit/auth/__init__.py b/tests/unit/auth/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/unit/auth/__init__.py
+++ /dev/null
diff --git a/tests/unit/auth/conftest.py b/tests/unit/auth/conftest.py
deleted file mode 100644
index a7c64a8..0000000
--- a/tests/unit/auth/conftest.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Module for fixtures and test utilities"""
-import uuid
-import datetime
-from contextlib import contextmanager
-
-from gn3.auth.authentication.oauth2.models.oauth2token import OAuth2Token
-
-from .fixtures import * # pylint: disable=[wildcard-import,unused-wildcard-import]
-
-def get_tokeniser(user):
-    """Get contextmanager for mocking token acquisition."""
-    @contextmanager
-    def __token__(*args, **kwargs):# pylint: disable=[unused-argument]
-        yield {
-            usr.user_id: OAuth2Token(
-                token_id=uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"),
-                client=None, token_type="Bearer", access_token="123456ABCDE",
-                refresh_token=None, revoked=False, expires_in=864000,
-                user=usr, issued_at=int(datetime.datetime.now().timestamp()),
-                scope="profile group role resource register-client")
-        for usr in TEST_USERS
-        }[user.user_id]
-
-    return __token__
diff --git a/tests/unit/auth/fixtures/__init__.py b/tests/unit/auth/fixtures/__init__.py
deleted file mode 100644
index a675fc7..0000000
--- a/tests/unit/auth/fixtures/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""pytest's conftest as a module."""
-from .role_fixtures import *
-from .user_fixtures import *
-from .group_fixtures import *
-from .resource_fixtures import *
-# from .privilege_fixtures import *
-from .migration_fixtures import *
-from .oauth2_client_fixtures import *
diff --git a/tests/unit/auth/fixtures/group_fixtures.py b/tests/unit/auth/fixtures/group_fixtures.py
deleted file mode 100644
index d7bbc56..0000000
--- a/tests/unit/auth/fixtures/group_fixtures.py
+++ /dev/null
@@ -1,147 +0,0 @@
-"""Fixtures and utilities for group-related tests"""
-import uuid
-
-import pytest
-
-from gn3.auth import db
-from gn3.auth.authorisation.groups import Group, GroupRole
-from gn3.auth.authorisation.resources import Resource, ResourceCategory
-
-from .role_fixtures import RESOURCE_EDITOR_ROLE, RESOURCE_READER_ROLE
-
-TEST_GROUP_01 = Group(uuid.UUID("9988c21d-f02f-4d45-8966-22c968ac2fbf"),
-                      "TheTestGroup", {})
-TEST_GROUP_02 = Group(uuid.UUID("e37d59d7-c05e-4d67-b479-81e627d8d634"),
-                      "AnotherTestGroup", {})
-TEST_GROUPS = (TEST_GROUP_01, TEST_GROUP_02)
-
-TEST_RESOURCES_GROUP_01 = (
-    Resource(TEST_GROUPS[0], uuid.UUID("26ad1668-29f5-439d-b905-84d551f85955"),
-             "ResourceG01R01",
-             ResourceCategory(uuid.UUID("48056f84-a2a6-41ac-8319-0e1e212cba2a"),
-                              "genotype", "Genotype Dataset"),
-             True),
-    Resource(TEST_GROUPS[0], uuid.UUID("2130aec0-fefd-434d-92fd-9ca342348b2d"),
-             "ResourceG01R02",
-             ResourceCategory(uuid.UUID("548d684b-d4d1-46fb-a6d3-51a56b7da1b3"),
-                              "phenotype", "Phenotype (Publish) Dataset"),
-             False),
-    Resource(TEST_GROUPS[0], uuid.UUID("e9a1184a-e8b4-49fb-b713-8d9cbeea5b83"),
-             "ResourceG01R03",
-             ResourceCategory(uuid.UUID("fad071a3-2fc8-40b8-992b-cdefe7dcac79"),
-                              "mrna", "mRNA Dataset"),
-             False))
-
-TEST_RESOURCES_GROUP_02 = (
-    Resource(TEST_GROUPS[1], uuid.UUID("14496a1c-c234-49a2-978c-8859ea274054"),
-             "ResourceG02R01",
-             ResourceCategory(uuid.UUID("48056f84-a2a6-41ac-8319-0e1e212cba2a"),
-                              "genotype", "Genotype Dataset"),
-             False),
-    Resource(TEST_GROUPS[1], uuid.UUID("04ad9e09-94ea-4390-8a02-11f92999806b"),
-             "ResourceG02R02",
-             ResourceCategory(uuid.UUID("fad071a3-2fc8-40b8-992b-cdefe7dcac79"),
-                              "mrna", "mRNA Dataset"),
-             True))
-
-TEST_RESOURCES = TEST_RESOURCES_GROUP_01 + TEST_RESOURCES_GROUP_02
-TEST_RESOURCES_PUBLIC = (TEST_RESOURCES_GROUP_01[0], TEST_RESOURCES_GROUP_02[1])
-
-def __gtuple__(cursor):
-    return tuple(dict(row) for row in cursor.fetchall())
-
-@pytest.fixture(scope="function")
-def fxtr_group(conn_after_auth_migrations):# pylint: disable=[redefined-outer-name]
-    """Fixture: setup a test group."""
-    query = "INSERT INTO groups(group_id, group_name) VALUES (?, ?)"
-    with db.cursor(conn_after_auth_migrations) as cursor:
-        cursor.executemany(
-            query, tuple(
-                (str(group.group_id), group.group_name)
-                for group in TEST_GROUPS))
-
-    yield (conn_after_auth_migrations, TEST_GROUPS[0])
-
-    with db.cursor(conn_after_auth_migrations) as cursor:
-        cursor.executemany(
-            "DELETE FROM groups WHERE group_id=?",
-            ((str(group.group_id),) for group in TEST_GROUPS))
-
-@pytest.fixture(scope="function")
-def fxtr_users_in_group(fxtr_group, fxtr_users):# pylint: disable=[redefined-outer-name, unused-argument]
-    """Link the users to the groups."""
-    conn, all_users = fxtr_users
-    users = tuple(
-        user for user in all_users if user.email not in ("unaff@iliated.user",))
-    query_params = tuple(
-        (str(TEST_GROUP_01.group_id), str(user.user_id)) for user in users)
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO group_users(group_id, user_id) VALUES (?, ?)",
-            query_params)
-
-    yield (conn, TEST_GROUP_01, users)
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "DELETE FROM group_users WHERE group_id=? AND user_id=?",
-            query_params)
-
-@pytest.fixture(scope="function")
-def fxtr_group_roles(fxtr_group, fxtr_roles):# pylint: disable=[redefined-outer-name,unused-argument]
-    """Link roles to group"""
-    group_roles = (
-        GroupRole(uuid.UUID("9c25efb2-b477-4918-a95c-9914770cbf4d"),
-                  TEST_GROUP_01, RESOURCE_EDITOR_ROLE),
-        GroupRole(uuid.UUID("82aed039-fe2f-408c-ab1e-81cd1ba96630"),
-                  TEST_GROUP_02, RESOURCE_READER_ROLE))
-    conn, groups = fxtr_group
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO group_roles VALUES (?, ?, ?)",
-            ((str(role.group_role_id), str(role.group.group_id),
-              str(role.role.role_id))
-             for role in group_roles))
-
-    yield conn, groups, group_roles
-
-    with db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM group_user_roles_on_resources")
-        cursor.executemany(
-            ("DELETE FROM group_roles "
-             "WHERE group_role_id=? AND group_id=? AND role_id=?"),
-            ((str(role.group_role_id), str(role.group.group_id),
-              str(role.role.role_id))
-             for role in group_roles))
-
-@pytest.fixture(scope="function")
-def fxtr_group_user_roles(fxtr_resources, fxtr_group_roles, fxtr_users_in_group):#pylint: disable=[redefined-outer-name,unused-argument]
-    """Assign roles to users."""
-    conn, _groups, group_roles = fxtr_group_roles
-    _conn, group_resources = fxtr_resources
-    _conn, _group, group_users = fxtr_users_in_group
-    users = tuple(user for user in group_users if user.email
-                  not in ("unaff@iliated.user", "group@lead.er"))
-    users_roles_resources = (
-        (user, RESOURCE_EDITOR_ROLE, TEST_RESOURCES_GROUP_01[1])
-        for user in users if user.email == "group@mem.ber01")
-    with db.cursor(conn) as cursor:
-        params = tuple({
-            "group_id": str(resource.group.group_id),
-            "user_id": str(user.user_id),
-            "role_id": str(role.role_id),
-            "resource_id": str(resource.resource_id)
-        } for user, role, resource in users_roles_resources)
-        cursor.executemany(
-            ("INSERT INTO group_user_roles_on_resources "
-             "VALUES (:group_id, :user_id, :role_id, :resource_id)"),
-            params)
-
-    yield conn, group_users, group_roles, group_resources
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            ("DELETE FROM group_user_roles_on_resources WHERE "
-             "group_id=:group_id AND user_id=:user_id AND role_id=:role_id AND "
-             "resource_id=:resource_id"),
-            params)
diff --git a/tests/unit/auth/fixtures/migration_fixtures.py b/tests/unit/auth/fixtures/migration_fixtures.py
deleted file mode 100644
index eb42c2b..0000000
--- a/tests/unit/auth/fixtures/migration_fixtures.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Fixtures and utilities for migration-related tests"""
-import pytest
-from yoyo.backends import DatabaseBackend
-from yoyo import get_backend, read_migrations
-from yoyo.migrations import Migration, MigrationList
-
-from gn3.auth import db
-from gn3.migrations import apply_migrations, rollback_migrations
-
-@pytest.fixture(scope="session")
-def auth_testdb_path(fxtr_app_config): # pylint: disable=redefined-outer-name
-    """Get the test application's auth database file"""
-    return fxtr_app_config["AUTH_DB"]
-
-@pytest.fixture(scope="session")
-def auth_migrations_dir(fxtr_app_config): # pylint: disable=redefined-outer-name
-    """Get the test application's auth database file"""
-    return fxtr_app_config["AUTH_MIGRATIONS"]
-
-def apply_single_migration(backend: DatabaseBackend, migration: Migration):# pylint: disable=[redefined-outer-name]
-    """Utility to apply a single migration"""
-    apply_migrations(backend, MigrationList([migration]))
-
-def rollback_single_migration(backend: DatabaseBackend, migration: Migration):# pylint: disable=[redefined-outer-name]
-    """Utility to rollback a single migration"""
-    rollback_migrations(backend, MigrationList([migration]))
-
-@pytest.fixture(scope="session")
-def backend(auth_testdb_path):# pylint: disable=redefined-outer-name
-    """Fixture: retrieve yoyo backend for auth database"""
-    return get_backend(f"sqlite:///{auth_testdb_path}")
-
-@pytest.fixture(scope="session")
-def all_migrations(auth_migrations_dir): # pylint: disable=redefined-outer-name
-    """Retrieve all the migrations"""
-    return read_migrations(auth_migrations_dir)
-
-@pytest.fixture(scope="function")
-def conn_after_auth_migrations(backend, auth_testdb_path, all_migrations): # pylint: disable=redefined-outer-name
-    """Run all migrations and return a connection to the database after"""
-    apply_migrations(backend, all_migrations)
-    with db.connection(auth_testdb_path) as conn:
-        yield conn
-
-    rollback_migrations(backend, all_migrations)
-
-def migrations_up_to(migration, migrations_dir):
-    """Run all the migration before `migration`."""
-    migrations = read_migrations(migrations_dir)
-    index = [mig.path for mig in migrations].index(migration)
-    return MigrationList(migrations[0:index])
diff --git a/tests/unit/auth/fixtures/oauth2_client_fixtures.py b/tests/unit/auth/fixtures/oauth2_client_fixtures.py
deleted file mode 100644
index 654d048..0000000
--- a/tests/unit/auth/fixtures/oauth2_client_fixtures.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Fixtures for OAuth2 clients"""
-import uuid
-import json
-import datetime
-
-import pytest
-
-from gn3.auth import db
-from gn3.auth.authentication.users import hash_password
-from gn3.auth.authentication.oauth2.models.oauth2client import OAuth2Client
-
-@pytest.fixture(autouse=True)
-def fxtr_patch_envvars(monkeypatch):
-    """Fixture: patch environment variable"""
-    monkeypatch.setenv("AUTHLIB_INSECURE_TRANSPORT", "true")
-
-@pytest.fixture
-def fxtr_oauth2_clients(fxtr_users_with_passwords):
-    """Fixture: Create the OAuth2 clients for use with tests."""
-    conn, users = fxtr_users_with_passwords
-    now = datetime.datetime.now()
-
-    clients = tuple(
-        OAuth2Client(str(uuid.uuid4()), f"yabadabadoo_{idx:03}", now,
-         now + datetime.timedelta(hours = 2),
-         {
-             "client_name": f"test_client_{idx:03}",
-             "scope": ["profile", "group", "role", "resource", "register-client"],
-             "redirect_uri": "/test_oauth2",
-             "token_endpoint_auth_method": [
-                 "client_secret_post", "client_secret_basic"],
-             "grant_types": ["password", "authorisation_code", "refresh_token"],
-            "response_type": "token"
-         }, user)
-        for idx, user  in enumerate(users, start=1))
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO oauth2_clients VALUES (?, ?, ?, ?, ?, ?)",
-            ((str(client.client_id), hash_password(client.client_secret),
-              int(client.client_id_issued_at.timestamp()),
-              int(client.client_secret_expires_at.timestamp()),
-              json.dumps(client.client_metadata), str(client.user.user_id))
-            for client in clients))
-
-    yield conn, clients
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "DELETE FROM oauth2_clients WHERE client_id=?",
-            ((str(client.client_id),) for client in clients))
diff --git a/tests/unit/auth/fixtures/resource_fixtures.py b/tests/unit/auth/fixtures/resource_fixtures.py
deleted file mode 100644
index 117b4f4..0000000
--- a/tests/unit/auth/fixtures/resource_fixtures.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""Fixtures and utilities for resource-related tests"""
-import pytest
-
-from gn3.auth import db
-
-from .group_fixtures import TEST_RESOURCES
-
-@pytest.fixture(scope="function")
-def fxtr_resources(fxtr_group):# pylint: disable=[redefined-outer-name]
-    """fixture: setup test resources in the database"""
-    conn, _group = fxtr_group
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO resources VALUES (?,?,?,?,?)",
-        ((str(res.group.group_id), str(res.resource_id), res.resource_name,
-          str(res.resource_category.resource_category_id),
-          1 if res.public else 0) for res in TEST_RESOURCES))
-
-    yield (conn, TEST_RESOURCES)
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "DELETE FROM resources WHERE group_id=? AND resource_id=?",
-        ((str(res.group.group_id), str(res.resource_id),)
-         for res in TEST_RESOURCES))
diff --git a/tests/unit/auth/fixtures/role_fixtures.py b/tests/unit/auth/fixtures/role_fixtures.py
deleted file mode 100644
index ee86aa2..0000000
--- a/tests/unit/auth/fixtures/role_fixtures.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""Fixtures and utilities for role-related tests"""
-import uuid
-
-import pytest
-
-from gn3.auth import db
-from gn3.auth.authorisation.roles import Role
-from gn3.auth.authorisation.privileges import Privilege
-
-RESOURCE_READER_ROLE = Role(
-    uuid.UUID("c3ca2507-ee24-4835-9b31-8c21e1c072d3"), "resource_reader", True,
-    (Privilege("group:resource:view-resource",
-               "view a resource and use it in computations"),))
-
-RESOURCE_EDITOR_ROLE = Role(
-    uuid.UUID("89819f84-6346-488b-8955-86062e9eedb7"), "resource_editor", True,
-    (
-        Privilege("group:resource:view-resource",
-                  "view a resource and use it in computations"),
-        Privilege("group:resource:edit-resource", "edit/update a resource")))
-
-TEST_ROLES = (RESOURCE_READER_ROLE, RESOURCE_EDITOR_ROLE)
-
-@pytest.fixture(scope="function")
-def fxtr_roles(conn_after_auth_migrations):
-    """Setup some example roles."""
-    with db.cursor(conn_after_auth_migrations) as cursor:
-        cursor.executemany(
-            ("INSERT INTO roles VALUES (?, ?, ?)"),
-            ((str(role.role_id), role.role_name, 1) for role in TEST_ROLES))
-        cursor.executemany(
-            ("INSERT INTO role_privileges VALUES (?, ?)"),
-            ((str(role.role_id), str(privilege.privilege_id))
-             for role in TEST_ROLES for privilege in role.privileges))
-
-    yield conn_after_auth_migrations, TEST_ROLES
-
-    with db.cursor(conn_after_auth_migrations) as cursor:
-        cursor.executemany(
-            ("DELETE FROM role_privileges WHERE role_id=? AND privilege_id=?"),
-            ((str(role.role_id), str(privilege.privilege_id))
-             for role in TEST_ROLES for privilege in role.privileges))
-        cursor.executemany(
-            ("DELETE FROM roles WHERE role_id=?"),
-            ((str(role.role_id),) for role in TEST_ROLES))
diff --git a/tests/unit/auth/fixtures/user_fixtures.py b/tests/unit/auth/fixtures/user_fixtures.py
deleted file mode 100644
index d248f54..0000000
--- a/tests/unit/auth/fixtures/user_fixtures.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""Fixtures and utilities for user-related tests"""
-import uuid
-
-import pytest
-
-from gn3.auth import db
-from gn3.auth.authentication.users import User, hash_password
-
-TEST_USERS = (
-        User(uuid.UUID("ecb52977-3004-469e-9428-2a1856725c7f"), "group@lead.er",
-             "Group Leader"),
-        User(uuid.UUID("21351b66-8aad-475b-84ac-53ce528451e3"),
-             "group@mem.ber01", "Group Member 01"),
-        User(uuid.UUID("ae9c6245-0966-41a5-9a5e-20885a96bea7"),
-             "group@mem.ber02", "Group Member 02"),
-        User(uuid.UUID("9a0c7ce5-2f40-4e78-979e-bf3527a59579"),
-             "unaff@iliated.user", "Unaffiliated User"))
-
-@pytest.fixture(scope="function")
-def fxtr_users(conn_after_auth_migrations):# pylint: disable=[redefined-outer-name]
-    """Fixture: setup test users."""
-    query = "INSERT INTO users(user_id, email, name) VALUES (?, ?, ?)"
-    query_user_roles = "INSERT INTO user_roles(user_id, role_id) VALUES (?, ?)"
-    test_user_roles = (
-        ("ecb52977-3004-469e-9428-2a1856725c7f",
-         "a0e67630-d502-4b9f-b23f-6805d0f30e30"),
-        ("ecb52977-3004-469e-9428-2a1856725c7f",
-         "ade7e6b0-ba9c-4b51-87d0-2af7fe39a347"))
-    with db.cursor(conn_after_auth_migrations) as cursor:
-        cursor.executemany(query, (
-            (str(user.user_id), user.email, user.name) for user in TEST_USERS))
-        cursor.executemany(query_user_roles, test_user_roles)
-
-    yield (conn_after_auth_migrations, TEST_USERS)
-
-    with db.cursor(conn_after_auth_migrations) as cursor:
-        cursor.executemany(
-            "DELETE FROM user_roles WHERE user_id=?",
-            (("ecb52977-3004-469e-9428-2a1856725c7f",),))
-        cursor.executemany(
-            "DELETE FROM users WHERE user_id=?",
-            (("ecb52977-3004-469e-9428-2a1856725c7f",),
-             ("21351b66-8aad-475b-84ac-53ce528451e3",),
-             ("ae9c6245-0966-41a5-9a5e-20885a96bea7",),
-             ("9a0c7ce5-2f40-4e78-979e-bf3527a59579",)))
-
-@pytest.fixture(scope="function")
-def fxtr_users_with_passwords(fxtr_users): # pylint: disable=[redefined-outer-name]
-    """Fixture: add passwords to the users"""
-    conn, users = fxtr_users
-    user_passwords_params = tuple(
-        (str(user.user_id), hash_password(
-            f"password_for_user_{idx:03}".encode("utf8")))
-        for idx, user in enumerate(users, start=1))
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "INSERT INTO user_credentials VALUES (?, ?)",
-            user_passwords_params)
-
-    yield conn, users
-
-    with db.cursor(conn) as cursor:
-        cursor.executemany(
-            "DELETE FROM user_credentials WHERE user_id=?",
-            ((item[0],) for item in user_passwords_params))
diff --git a/tests/unit/auth/test_credentials.py b/tests/unit/auth/test_credentials.py
deleted file mode 100644
index f2a3d25..0000000
--- a/tests/unit/auth/test_credentials.py
+++ /dev/null
@@ -1,100 +0,0 @@
-"""Test the credentials checks"""
-import pytest
-from yoyo.migrations import MigrationList
-from hypothesis import given, settings, strategies, HealthCheck
-
-from gn3.auth import db
-from gn3.auth.authentication import credentials_in_database
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-
-from tests.unit.auth.conftest import migrations_up_to
-
-@pytest.fixture
-def with_credentials_table(backend, auth_testdb_path):
-    """
-    Fixture: Yield a connection object with the 'user_credentials' table
-    created.
-    """
-    migrations_dir = "migrations/auth"
-    migration = f"{migrations_dir}/20221103_02_sGrIs-create-user-credentials-table.py"
-    migrations = (migrations_up_to(migration, migrations_dir) +
-                  MigrationList([get_migration(migration)]))
-    apply_migrations(backend, migrations)
-    with db.connection(auth_testdb_path) as conn:
-        yield conn
-
-    rollback_migrations(backend, migrations)
-
-@pytest.fixture
-def with_credentials(with_credentials_table):# pylint: disable=redefined-outer-name
-    """
-    Fixture: Initialise the database with some user credentials.
-    """
-    with db.cursor(with_credentials_table) as cursor:
-        cursor.executemany(
-            "INSERT INTO users VALUES (:user_id, :email, :name)",
-            ({"user_id": "82552014-21ee-4321-b96a-b8788b97b862",
-              "email": "first@test.user",
-              "name": "First Test User"
-              },
-             {"user_id": "bdd5cb7a-072d-4c2b-9872-d0cecb718523",
-              "email": "second@test.user",
-              "name": "Second Test User"
-              }))
-        cursor.executemany(
-            "INSERT INTO user_credentials VALUES (:user_id, :password)",
-            ({"user_id": "82552014-21ee-4321-b96a-b8788b97b862",
-              "password": b'$2b$12$LAh1PYtUgAFK7d5fA0EfL.4AdTZuYEAfzwO.p.jXVboxcP8bXNj7a'
-              },
-             {"user_id": "bdd5cb7a-072d-4c2b-9872-d0cecb718523",
-              "password": b'$2b$12$zX77QCFSJuwIjAZGc0Jq5.rCWMHEMKD9Zf3Ay4C0AzwsiZ7SSPdKO'
-              }))
-
-        yield with_credentials_table
-
-        cursor.executemany("DELETE FROM user_credentials WHERE user_id=?",
-                           (("82552014-21ee-4321-b96a-b8788b97b862",),
-                            ("bdd5cb7a-072d-4c2b-9872-d0cecb718523",)))
-        cursor.executemany("DELETE FROM users WHERE user_id=?",
-                           (("82552014-21ee-4321-b96a-b8788b97b862",),
-                            ("bdd5cb7a-072d-4c2b-9872-d0cecb718523",)))
-
-@pytest.mark.unit_test
-@given(strategies.emails(), strategies.text())
-@settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
-def test_credentials_not_in_database(with_credentials, email, password):# pylint: disable=redefined-outer-name
-    """
-    GIVEN: credentials that do not exist in the database
-    WHEN: the `credentials_in_database` function is run against the credentials
-    THEN: check that the function returns false in all cases.
-    """
-    with db.cursor(with_credentials) as cursor:
-        assert credentials_in_database(cursor, email, password) is False
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "email,password",
-    (("first@test.user", "wrongpassword"),
-     ("first@tes.user", "testuser01")))
-def test_partially_wrong_credentials(with_credentials, email, password):# pylint: disable=redefined-outer-name
-    """
-    GIVEN: credentials that exist in the database
-    WHEN: the credentials are checked with partially wrong values
-    THEN: the check fails since the credentials are not correct
-    """
-    with db.cursor(with_credentials) as cursor:
-        assert credentials_in_database(cursor, email, password) is False
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "email,password",
-    (("first@test.user", "testuser01"),
-     ("second@test.user", "testuser02")))
-def test_partially_correct_credentials(with_credentials, email, password):# pylint: disable=redefined-outer-name
-    """
-    GIVEN: credentials that exist in the database
-    WHEN: the credentials are checked with correct values
-    THEN: the check passes
-    """
-    with db.cursor(with_credentials) as cursor:
-        assert credentials_in_database(cursor, email, password) is True
diff --git a/tests/unit/auth/test_groups.py b/tests/unit/auth/test_groups.py
deleted file mode 100644
index d3b8fd4..0000000
--- a/tests/unit/auth/test_groups.py
+++ /dev/null
@@ -1,171 +0,0 @@
-"""Test functions dealing with group management."""
-from uuid import UUID
-
-import pytest
-from pymonad.maybe import Nothing
-
-from gn3.auth import db
-from gn3.auth.authentication.users import User
-from gn3.auth.authorisation.roles import Role
-from gn3.auth.authorisation.privileges import Privilege
-from gn3.auth.authorisation.errors import AuthorisationError
-from gn3.auth.authorisation.groups.models import (
-    Group, GroupRole, user_group, create_group, create_group_role)
-
-from tests.unit.auth import conftest
-
-create_group_failure = {
-    "status": "error",
-    "message": "Unauthorised: Failed to create group."
-}
-
-def uuid_fn():
-    """Mock function for uuid"""
-    return UUID("d32611e3-07fc-4564-b56c-786c6db6de2b")
-
-
-GROUP = Group(UUID("9988c21d-f02f-4d45-8966-22c968ac2fbf"), "TheTestGroup",
-              {"group_description": "The test group"})
-PRIVILEGES = (
-    Privilege(
-        "group:resource:view-resource",
-        "view a resource and use it in computations"),
-    Privilege("group:resource:edit-resource", "edit/update a resource"))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected", tuple(zip(conftest.TEST_USERS[0:1], (
-        Group(
-            UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"), "a_test_group",
-            {"group_description": "A test group"}),
-        create_group_failure, create_group_failure, create_group_failure,
-        create_group_failure))))
-def test_create_group(# pylint: disable=[too-many-arguments]
-        fxtr_app, auth_testdb_path, mocker, fxtr_users, user, expected):# pylint: disable=[unused-argument]
-    """
-    GIVEN: an authenticated user
-    WHEN: the user attempts to create a group
-    THEN: verify they are only able to create the group if they have the
-          appropriate privileges
-    """
-    mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    with db.connection(auth_testdb_path) as conn:
-        assert create_group(
-            conn, "a_test_group", user, "A test group") == expected
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("user", conftest.TEST_USERS[1:])
-def test_create_group_raises_exception_with_non_privileged_user(# pylint: disable=[too-many-arguments]
-        fxtr_app, auth_testdb_path, mocker, fxtr_users, user):# pylint: disable=[unused-argument]
-    """
-    GIVEN: an authenticated user, without appropriate privileges
-    WHEN: the user attempts to create a group
-    THEN: verify the system raises an exception
-    """
-    mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    with db.connection(auth_testdb_path) as conn:
-        with pytest.raises(AuthorisationError):
-            assert create_group(conn, "a_test_group", user, "A test group")
-
-create_role_failure = {
-    "status": "error",
-    "message": "Unauthorised: Could not create the group role"
-}
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected", tuple(zip(conftest.TEST_USERS[0:1], (
-        GroupRole(
-            UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"),
-            GROUP,
-            Role(UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"),
-                 "ResourceEditor", True, PRIVILEGES)),))))
-def test_create_group_role(mocker, fxtr_users_in_group, user, expected):
-    """
-    GIVEN: an authenticated user
-    WHEN: the user attempts to create a role, attached to a group
-    THEN: verify they are only able to create the role if they have the
-        appropriate privileges and that the role is attached to the given group
-    """
-    mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    conn, _group, _users = fxtr_users_in_group
-    with db.cursor(conn) as cursor:
-        assert create_group_role(
-            conn, GROUP, "ResourceEditor", PRIVILEGES) == expected
-        # cleanup
-        cursor.execute(
-            ("DELETE FROM group_roles "
-             "WHERE group_role_id=? AND group_id=? AND role_id=?"),
-            (str(uuid_fn()), str(GROUP.group_id), str(uuid_fn())))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected", tuple(zip(conftest.TEST_USERS[1:], (
-        create_role_failure, create_role_failure, create_role_failure))))
-def test_create_group_role_raises_exception_with_unauthorised_users(
-        mocker, fxtr_users_in_group, user, expected):
-    """
-    GIVEN: an authenticated user
-    WHEN: the user attempts to create a role, attached to a group
-    THEN: verify they are only able to create the role if they have the
-        appropriate privileges and that the role is attached to the given group
-    """
-    mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    conn, _group, _users = fxtr_users_in_group
-    with pytest.raises(AuthorisationError):
-        assert create_group_role(
-            conn, GROUP, "ResourceEditor", PRIVILEGES) == expected
-
-@pytest.mark.unit_test
-def test_create_multiple_groups(mocker, fxtr_users):
-    """
-    GIVEN: An authenticated user with appropriate authorisation
-    WHEN: The user attempts to create a new group, while being a member of an
-      existing group
-    THEN: The system should prevent that, and respond with an appropriate error
-      message
-    """
-    mocker.patch("gn3.auth.authorisation.groups.models.uuid4", uuid_fn)
-    user = User(
-        UUID("ecb52977-3004-469e-9428-2a1856725c7f"), "group@lead.er",
-        "Group Leader")
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    conn, _test_users = fxtr_users
-    # First time, successfully creates the group
-    assert create_group(conn, "a_test_group", user) == Group(
-        UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"), "a_test_group",
-        {})
-    # subsequent attempts should fail
-    with pytest.raises(AuthorisationError):
-        create_group(conn, "another_test_group", user)
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected",
-    tuple(zip(
-        conftest.TEST_USERS,
-        (([Group(UUID("9988c21d-f02f-4d45-8966-22c968ac2fbf"), "TheTestGroup", {})] * 3)
-         + [Nothing]))))
-def test_user_group(fxtr_users_in_group, user, expected):
-    """
-    GIVEN: A bunch of registered users, some of whom are members of a group, and
-      others are not
-    WHEN: a particular user's group is requested,
-    THEN: return a Maybe containing the group that the user belongs to, or
-      Nothing
-    """
-    conn, _group, _users = fxtr_users_in_group
-    assert (
-        user_group(conn, user).maybe(Nothing, lambda val: val)
-        == expected)
diff --git a/tests/unit/auth/test_migrations_add_data_to_table.py b/tests/unit/auth/test_migrations_add_data_to_table.py
deleted file mode 100644
index 9cb5d0c..0000000
--- a/tests/unit/auth/test_migrations_add_data_to_table.py
+++ /dev/null
@@ -1,79 +0,0 @@
-"""Test data insertion when migrations are run."""
-import pytest
-
-from gn3.auth import db
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-test_params = (
-    ("20221116_01_nKUmX-add-privileges-to-group-leader-role.py",
-     ("SELECT role_id, privilege_id FROM role_privileges "
-      "WHERE role_id=? AND privilege_id IN (?, ?, ?, ?)"),
-     ("a0e67630-d502-4b9f-b23f-6805d0f30e30",
-      "221660b1-df05-4be1-b639-f010269dbda9",
-      "7bcca363-cba9-4169-9e31-26bdc6179b28",
-      "5103cc68-96f8-4ebb-83a4-a31692402c9b",
-      "1c59eff5-9336-4ed2-a166-8f70d4cb012e"),
-     (("a0e67630-d502-4b9f-b23f-6805d0f30e30",
-       "221660b1-df05-4be1-b639-f010269dbda9"),
-      ("a0e67630-d502-4b9f-b23f-6805d0f30e30",
-       "7bcca363-cba9-4169-9e31-26bdc6179b28"),
-      ("a0e67630-d502-4b9f-b23f-6805d0f30e30",
-       "5103cc68-96f8-4ebb-83a4-a31692402c9b"),
-      ("a0e67630-d502-4b9f-b23f-6805d0f30e30",
-       "1c59eff5-9336-4ed2-a166-8f70d4cb012e"))),)
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("migration_file,query,query_params,data", test_params)
-def test_apply_insert(# pylint: disable=[too-many-arguments]
-        auth_migrations_dir, backend, auth_testdb_path, migration_file, query,
-        query_params, data):
-    """
-    GIVEN: a database migration script
-    WHEN: the script is applied
-    THEN: ensure the given data exists in the table
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path, None) as conn, db.cursor(conn) as cursor:
-        cursor.execute(query, query_params)
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(query, query_params)
-        result_after_migration = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations + [the_migration])
-    assert len(result_before_migration) == 0, "Expected no results before migration"
-    assert sorted(result_after_migration) == sorted(data)
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("migration_file,query,query_params,data", test_params)
-def test_rollback_insert(# pylint: disable=[too-many-arguments]
-        auth_migrations_dir, backend, auth_testdb_path, migration_file, query,
-        query_params, data):
-    """
-    GIVEN: a database migration script
-    WHEN: the script is rolled back
-    THEN: ensure the given data no longer exists in the database
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path, None) as conn, db.cursor(conn) as cursor:
-        cursor.execute(query, query_params)
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(query, query_params)
-        result_after_migration = cursor.fetchall()
-        rollback_single_migration(backend, the_migration)
-        cursor.execute(query, query_params)
-        result_after_rollback = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations)
-    assert len(result_before_migration) == 0, "Expected no results before migration"
-    assert sorted(result_after_migration) == sorted(data)
-    assert len(result_after_rollback) == 0, "Expected no results after rollback"
diff --git a/tests/unit/auth/test_migrations_add_remove_columns.py b/tests/unit/auth/test_migrations_add_remove_columns.py
deleted file mode 100644
index ea9bf7b..0000000
--- a/tests/unit/auth/test_migrations_add_remove_columns.py
+++ /dev/null
@@ -1,116 +0,0 @@
-"""Test migrations that alter tables adding/removing columns."""
-import pytest
-
-from gn3.auth import db
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-QUERY = "SELECT sql FROM sqlite_schema WHERE name=?"
-
-TEST_PARAMS = (
-    ("20221109_01_HbD5F-add-resource-meta-field-to-resource-categories-field.py",
-     "resource_categories", "resource_meta TEXT", True),
-    (("20221110_08_23psB-add-privilege-category-and-privilege-description-"
-      "columns-to-privileges-table.py"),
-     "privileges", "privilege_category TEXT", True),
-    (("20221110_08_23psB-add-privilege-category-and-privilege-description-"
-      "columns-to-privileges-table.py"),
-     "privileges", "privilege_description TEXT", True),
-    ("20221117_01_RDlfx-modify-group-roles-add-group-role-id.py", "group_roles",
-     "group_role_id", True),
-    ("20221208_01_sSdHz-add-public-column-to-resources-table.py", "resources",
-     "public", True))
-
-def found(haystack: str, needle: str) -> bool:
-    """Check whether `needle` is found in `haystack`"""
-    return any(
-        (line.strip().find(needle) >= 0) for line in haystack.split("\n"))
-
-def pristine_before_migration(adding: bool, result_str: str, column: str) -> bool:
-    """Check that database is pristine before running the migration"""
-    col_was_found = found(result_str, column)
-    if adding:
-        return not col_was_found
-    return col_was_found
-
-def applied_successfully(adding: bool, result_str: str, column: str) -> bool:
-    """Check that the migration ran successfully"""
-    col_was_found = found(result_str, column)
-    if adding:
-        return col_was_found
-    return not col_was_found
-
-def rolled_back_successfully(adding: bool, result_str: str, column: str) -> bool:
-    """Check that the migration ran successfully"""
-    col_was_found = found(result_str, column)
-    if adding:
-        return not col_was_found
-    return col_was_found
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "migration_file,the_table,the_column,adding", TEST_PARAMS)
-def test_apply_add_remove_column(# pylint: disable=[too-many-arguments]
-        auth_migrations_dir, auth_testdb_path, backend, migration_file,
-        the_table, the_column, adding):
-    """
-    GIVEN: A migration that alters a table, adding or removing a column
-    WHEN: The migration is applied
-    THEN: Ensure the column exists if `adding` is True, otherwise, ensure the
-          column has been dropped
-    """
-    migration_path = f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        cursor.execute(QUERY, (the_table,))
-        results_before_migration = cursor.fetchone()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(QUERY, (the_table,))
-        results_after_migration = cursor.fetchone()
-
-    rollback_migrations(backend, older_migrations + [the_migration])
-
-    assert pristine_before_migration(
-        adding, results_before_migration[0], the_column), (
-            f"Column `{the_column}` exists before migration and should not"
-            if adding else
-            f"Column `{the_column}` doesn't exist before migration and it should")
-    assert applied_successfully(
-        adding, results_after_migration[0], the_column), "Migration failed"
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "migration_file,the_table,the_column,adding", TEST_PARAMS)
-def test_rollback_add_remove_column(# pylint: disable=[too-many-arguments]
-        auth_migrations_dir, auth_testdb_path, backend, migration_file,
-        the_table, the_column, adding):
-    """
-    GIVEN: A migration that alters a table, adding or removing a column
-    WHEN: The migration is applied
-    THEN: Ensure the column is dropped if `adding` is True, otherwise, ensure
-          the column has been restored
-    """
-    migration_path = f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    apply_single_migration(backend, the_migration)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        cursor.execute(QUERY, (the_table,))
-        results_before_rollback = cursor.fetchone()
-        rollback_single_migration(backend, the_migration)
-        cursor.execute(QUERY, (the_table,))
-        results_after_rollback = cursor.fetchone()
-
-    rollback_migrations(backend, older_migrations + [the_migration])
-
-    assert pristine_before_migration(
-        not adding, results_before_rollback[0], the_column), (
-            f"Column `{the_column}` doesn't exist before rollback and it should"
-            if adding else
-            f"Column `{the_column}` exists before rollback and should not")
-    assert rolled_back_successfully(
-        adding, results_after_rollback[0], the_column), "Rollback failed"
diff --git a/tests/unit/auth/test_migrations_create_tables.py b/tests/unit/auth/test_migrations_create_tables.py
deleted file mode 100644
index 2b8140b..0000000
--- a/tests/unit/auth/test_migrations_create_tables.py
+++ /dev/null
@@ -1,91 +0,0 @@
-"""Test migrations that create tables"""
-import pytest
-
-from gn3.auth import db
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-migrations_and_tables = (
-    ("20221103_01_js9ub-initialise-the-auth-entic-oris-ation-database.py",
-     "users"),
-    ("20221103_02_sGrIs-create-user-credentials-table.py", "user_credentials"),
-    ("20221108_01_CoxYh-create-the-groups-table.py", "groups"),
-    ("20221108_02_wxTr9-create-privileges-table.py", "privileges"),
-    ("20221108_03_Pbhb1-create-resource-categories-table.py", "resource_categories"),
-    ("20221110_01_WtZ1I-create-resources-table.py", "resources"),
-    ("20221110_05_BaNtL-create-roles-table.py", "roles"),
-    ("20221110_06_Pq2kT-create-generic-roles-table.py", "generic_roles"),
-    ("20221110_07_7WGa1-create-role-privileges-table.py", "role_privileges"),
-    ("20221114_01_n8gsF-create-generic-role-privileges-table.py",
-     "generic_role_privileges"),
-    ("20221114_03_PtWjc-create-group-roles-table.py", "group_roles"),
-    ("20221114_05_hQun6-create-user-roles-table.py", "user_roles"),
-    ("20221117_02_fmuZh-create-group-users-table.py", "group_users"),
-    ("20221206_01_BbeF9-create-group-user-roles-on-resources-table.py",
-     "group_user_roles_on_resources"),
-    ("20221219_01_CI3tN-create-oauth2-clients-table.py", "oauth2_clients"),
-    ("20221219_02_buSEU-create-oauth2-tokens-table.py", "oauth2_tokens"),
-    ("20221219_03_PcTrb-create-authorisation-code-table.py",
-     "authorisation_code"),
-    ("20230207_01_r0bkZ-create-group-join-requests-table.py",
-     "group_join_requests"),
-    ("20230322_01_0dDZR-create-linked-phenotype-data-table.py",
-     "linked_phenotype_data"),
-    ("20230322_02_Ll854-create-phenotype-resources-table.py",
-     "phenotype_resources"),
-    ("20230404_01_VKxXg-create-linked-genotype-data-table.py",
-     "linked_genotype_data"),
-    ("20230404_02_la33P-create-genotype-resources-table.py",
-     "genotype_resources"),
-    ("20230410_01_8mwaf-create-linked-mrna-data-table.py", "linked_mrna_data"),
-    ("20230410_02_WZqSf-create-mrna-resources-table.py", "mrna_resources"))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("migration_file,the_table", migrations_and_tables)
-def test_create_table(
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        the_table):
-    """
-    GIVEN: A database migration script to create table, `the_table`
-    WHEN: The migration is applied
-    THEN: Ensure that the table `the_table` is created
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, get_migration(migration_path))
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_after_migration = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations)
-    assert the_table not in [row[0] for row in result_before_migration]
-    assert the_table in [row[0] for row in result_after_migration]
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("migration_file,the_table", migrations_and_tables)
-def test_rollback_create_table(
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        the_table):
-    """
-    GIVEN: A database migration script to create the table `the_table`
-    WHEN: The migration is rolled back
-    THEN: Ensure that the table `the_table` no longer exists
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        apply_single_migration(backend, get_migration(migration_path))
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_after_migration = cursor.fetchall()
-        rollback_single_migration(backend, get_migration(migration_path))
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_after_rollback = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations)
-    assert the_table in [row[0] for row in result_after_migration]
-    assert the_table not in [row[0] for row in result_after_rollback]
diff --git a/tests/unit/auth/test_migrations_drop_tables.py b/tests/unit/auth/test_migrations_drop_tables.py
deleted file mode 100644
index 2362c77..0000000
--- a/tests/unit/auth/test_migrations_drop_tables.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""Test migrations that create tables"""
-
-import pytest
-
-from gn3.auth import db
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-test_params = (
-    ("20221114_02_DKKjn-drop-generic-role-tables.py", "generic_roles"),
-    ("20221114_02_DKKjn-drop-generic-role-tables.py", "generic_role_privileges"))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("migration_file,the_table", test_params)
-def test_drop_table(
-        auth_testdb_path, auth_migrations_dir, backend,
-        migration_file, the_table):
-    """
-    GIVEN: A database migration script to create table, `the_table`
-    WHEN: The migration is applied
-    THEN: Ensure that the table `the_table` is created
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_after_migration = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations + [the_migration])
-    assert the_table in [row[0] for row in result_before_migration]
-    assert the_table not in [row[0] for row in result_after_migration]
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize("migration_file,the_table", test_params)
-def test_rollback_drop_table(
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        the_table):
-    """
-    GIVEN: A database migration script to create the table `the_table`
-    WHEN: The migration is rolled back
-    THEN: Ensure that the table `the_table` no longer exists
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        apply_single_migration(backend, the_migration)
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_after_migration = cursor.fetchall()
-        rollback_single_migration(backend, the_migration)
-        cursor.execute("SELECT name FROM sqlite_schema WHERE type='table'")
-        result_after_rollback = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations)
-    assert the_table not in [row[0] for row in result_after_migration]
-    assert the_table in [row[0] for row in result_after_rollback]
diff --git a/tests/unit/auth/test_migrations_indexes.py b/tests/unit/auth/test_migrations_indexes.py
deleted file mode 100644
index b1f06d9..0000000
--- a/tests/unit/auth/test_migrations_indexes.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""Test that indexes are created and removed."""
-import pytest
-
-from gn3.auth import db
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-QUERY = """
-SELECT name FROM sqlite_master WHERE type='index' AND tbl_name = ?
-AND name= ?
-"""
-
-migrations_tables_and_indexes = (
-    ("20221110_07_7WGa1-create-role-privileges-table.py", "role_privileges",
-     "idx_tbl_role_privileges_cols_role_id"),
-    ("20221114_01_n8gsF-create-generic-role-privileges-table.py",
-     "generic_role_privileges",
-     "idx_tbl_generic_role_privileges_cols_generic_role_id"),
-    ("20221114_03_PtWjc-create-group-roles-table.py", "group_roles",
-     "idx_tbl_group_roles_cols_group_id"),
-    ("20221114_05_hQun6-create-user-roles-table.py", "user_roles",
-     "idx_tbl_user_roles_cols_user_id"),
-    ("20221117_02_fmuZh-create-group-users-table.py", "group_users",
-     "tbl_group_users_cols_group_id"),
-    ("20221206_01_BbeF9-create-group-user-roles-on-resources-table.py",
-     "group_user_roles_on_resources",
-     "idx_tbl_group_user_roles_on_resources_group_user_resource"))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "migration_file,the_table,the_index", migrations_tables_and_indexes)
-def test_index_created(# pylint: disable=[too-many-arguments]
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        the_table, the_index):
-    """
-    GIVEN: A database migration
-    WHEN: The migration is applied
-    THEN: Ensure the given index is created for the provided table
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    query_params = (the_table, the_index)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        cursor.execute(QUERY, query_params)
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(QUERY, query_params)
-        result_after_migration = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations + [the_migration])
-    assert the_index not in [row[0] for row in result_before_migration], (
-        f"Index '{the_index}' was found for table '{the_table}' before migration.")
-    assert (
-        len(result_after_migration) == 1
-        and result_after_migration[0][0] == the_index), (
-        f"Index '{the_index}' was not found for table '{the_table}' after migration.")
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "migration_file,the_table,the_index", migrations_tables_and_indexes)
-def test_index_dropped(# pylint: disable=[too-many-arguments]
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        the_table, the_index):
-    """
-    GIVEN: A database migration
-    WHEN: The migration is rolled-back
-    THEN: Ensure the given index no longer exists for the given table
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    query_params = (the_table, the_index)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        cursor.execute(QUERY, query_params)
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(QUERY, query_params)
-        result_after_migration = cursor.fetchall()
-        rollback_single_migration(backend, the_migration)
-        cursor.execute(QUERY, query_params)
-        result_after_rollback = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations)
-    assert the_index not in [row[0] for row in result_before_migration], (
-        f"Index '{the_index}' was found for table '{the_table}' before "
-        "migration")
-    assert (
-        len(result_after_migration) == 1
-        and result_after_migration[0][0] == the_index), (
-        f"Index '{the_index}' was not found for table '{the_table}' after migration.")
-    assert the_index not in [row[0] for row in result_after_rollback], (
-        f"Index '{the_index}' was found for table '{the_table}' after "
-        "rollback")
diff --git a/tests/unit/auth/test_migrations_init_data_in_resource_categories_table.py b/tests/unit/auth/test_migrations_init_data_in_resource_categories_table.py
deleted file mode 100644
index dd3d4c6..0000000
--- a/tests/unit/auth/test_migrations_init_data_in_resource_categories_table.py
+++ /dev/null
@@ -1,60 +0,0 @@
-"""
-Test that the `resource_categories` table is initialised with the startup data.
-"""
-import pytest
-
-from gn3.auth import db
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-MIGRATION_PATH = "migrations/auth/20221108_04_CKcSL-init-data-in-resource-categories-table.py"
-
-@pytest.mark.unit_test
-def test_apply_init_data(auth_testdb_path, auth_migrations_dir, backend):
-    """
-    GIVEN: A migration script
-    WHEN: The migration is applied
-    THEN: Verify that the expected data exists  in the table
-    """
-    older_migrations = migrations_up_to(MIGRATION_PATH, auth_migrations_dir)
-    the_migration = get_migration(MIGRATION_PATH)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path, None) as conn, db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM resource_categories")
-        assert len(cursor.fetchall()) == 0, "Expected empty table."
-        apply_single_migration(backend, the_migration)
-        cursor.execute("SELECT * FROM resource_categories")
-        results = cursor.fetchall()
-        assert len(results) == 3, "Expected 3 rows of data."
-        assert sorted(results) == sorted((
-            ('fad071a3-2fc8-40b8-992b-cdefe7dcac79', 'mrna', 'mRNA Dataset'),
-            ('548d684b-d4d1-46fb-a6d3-51a56b7da1b3', 'phenotype',
-             'Phenotype (Publish) Dataset'),
-            ('48056f84-a2a6-41ac-8319-0e1e212cba2a', 'genotype',
-             'Genotype Dataset')))
-
-    rollback_migrations(backend, older_migrations + [the_migration])
-
-@pytest.mark.unit_test
-def test_rollback_init_data(auth_testdb_path, auth_migrations_dir, backend):
-    """
-    GIVEN: A migration script
-    WHEN: The migration is rolled back
-    THEN: Verify that the table is empty
-    """
-    older_migrations = migrations_up_to(MIGRATION_PATH, auth_migrations_dir)
-    the_migration = get_migration(MIGRATION_PATH)
-    apply_migrations(backend, older_migrations)
-    with db.connection(auth_testdb_path, None) as conn, db.cursor(conn) as cursor:
-        cursor.execute("SELECT * FROM resource_categories")
-        assert len(cursor.fetchall()) == 0, "Expected empty table."
-        apply_single_migration(backend, the_migration)
-        cursor.execute("SELECT * FROM resource_categories")
-        results = cursor.fetchall()
-        assert len(results) == 3, "Expected 3 rows of data."
-        rollback_single_migration(backend, the_migration)
-        cursor.execute("SELECT * FROM resource_categories")
-        assert len(cursor.fetchall()) == 0, "Expected empty table."
-
-    rollback_migrations(backend, older_migrations)
diff --git a/tests/unit/auth/test_migrations_insert_data_into_empty_table.py b/tests/unit/auth/test_migrations_insert_data_into_empty_table.py
deleted file mode 100644
index ebb7fa6..0000000
--- a/tests/unit/auth/test_migrations_insert_data_into_empty_table.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""Test data insertion when migrations are run."""
-import sqlite3
-from contextlib import closing
-
-import pytest
-
-from gn3.migrations import get_migration, apply_migrations, rollback_migrations
-from tests.unit.auth.conftest import (
-    apply_single_migration, rollback_single_migration, migrations_up_to)
-
-test_params = (
-    ("20221113_01_7M0hv-enumerate-initial-privileges.py", "privileges", 19),
-    ("20221114_04_tLUzB-initialise-basic-roles.py", "roles", 2),
-    ("20221114_04_tLUzB-initialise-basic-roles.py", "role_privileges", 15))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "migration_file,table,row_count", test_params)
-def test_apply_insert(# pylint: disable=[too-many-arguments]
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        table, row_count):
-    """
-    GIVEN: A database migration
-    WHEN: The migration is applied
-    THEN: Ensure the given number of rows are inserted into the table
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with closing(sqlite3.connect(auth_testdb_path)) as conn, closing(conn.cursor()) as cursor:
-        query = f"SELECT COUNT(*) FROM {table}"
-        cursor.execute(query)
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(query)
-        result_after_migration = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations+[the_migration])
-    assert result_before_migration[0][0] == 0, (
-        "Expected empty table before initialisation")
-    assert result_after_migration[0][0] == row_count, (
-        f"Expected {row_count} rows")
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "migration_file,table,row_count", test_params)
-def test_rollback_insert(# pylint: disable=[too-many-arguments]
-        auth_testdb_path, auth_migrations_dir, backend, migration_file,
-        table, row_count):
-    """
-    GIVEN: A database migration
-    WHEN: The migration is applied
-    THEN: Ensure the given number of rows are inserted into the table
-    """
-    migration_path=f"{auth_migrations_dir}/{migration_file}"
-    older_migrations = migrations_up_to(migration_path, auth_migrations_dir)
-    the_migration = get_migration(migration_path)
-    apply_migrations(backend, older_migrations)
-    with closing(sqlite3.connect(auth_testdb_path)) as conn, closing(conn.cursor()) as cursor:
-        query = f"SELECT COUNT(*) FROM {table}"
-        cursor.execute(query)
-        result_before_migration = cursor.fetchall()
-        apply_single_migration(backend, the_migration)
-        cursor.execute(query)
-        result_after_migration = cursor.fetchall()
-        rollback_single_migration(backend, the_migration)
-        cursor.execute(query)
-        result_after_rollback = cursor.fetchall()
-
-    rollback_migrations(backend, older_migrations)
-    assert result_before_migration[0][0] == 0, (
-        "Expected empty table before initialisation")
-    assert result_after_migration[0][0] == row_count, (
-        f"Expected {row_count} rows")
-    assert result_after_rollback[0][0] == 0, (
-        "Expected empty table after rollback")
diff --git a/tests/unit/auth/test_privileges.py b/tests/unit/auth/test_privileges.py
deleted file mode 100644
index 3c645c7..0000000
--- a/tests/unit/auth/test_privileges.py
+++ /dev/null
@@ -1,45 +0,0 @@
-"""Test the privileges module"""
-import pytest
-
-from gn3.auth import db
-from gn3.auth.authorisation.privileges import Privilege, user_privileges
-
-from tests.unit.auth import conftest
-
-
-PRIVILEGES = sorted(
-    (Privilege("system:group:create-group", "Create a group"),
-     Privilege("system:group:view-group", "View the details of a group"),
-     Privilege("system:group:edit-group", "Edit the details of a group"),
-     Privilege("system:user:list", "List users in the system"),
-     Privilege("system:group:delete-group", "Delete a group"),
-     Privilege("group:user:add-group-member", "Add a user to a group"),
-     Privilege("group:user:remove-group-member", "Remove a user from a group"),
-     Privilege("system:group:transfer-group-leader",
-               "Transfer leadership of the group to some other member"),
-
-     Privilege("group:resource:create-resource", "Create a resource object"),
-     Privilege("group:resource:view-resource",
-               "view a resource and use it in computations"),
-     Privilege("group:resource:edit-resource", "edit/update a resource"),
-     Privilege("group:resource:delete-resource", "Delete a resource"),
-
-     Privilege("group:role:create-role", "Create a new role"),
-     Privilege("group:role:edit-role", "edit/update an existing role"),
-     Privilege("group:user:assign-role", "Assign a role to an existing user"),
-     Privilege("group:role:delete-role", "Delete an existing role")),
-    key=lambda x: x.privilege_id)
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected", tuple(zip(
-        conftest.TEST_USERS, (PRIVILEGES, [], [], [], []))))
-def test_user_privileges(auth_testdb_path, fxtr_users, user, expected):# pylint: disable=[unused-argument]
-    """
-    GIVEN: A user
-    WHEN: An attempt is made to fetch the user's privileges
-    THEN: Ensure only
-    """
-    with db.connection(auth_testdb_path) as conn:
-        assert sorted(
-            user_privileges(conn, user), key=lambda x: x.privilege_id) == expected
diff --git a/tests/unit/auth/test_resources.py b/tests/unit/auth/test_resources.py
deleted file mode 100644
index 7b9798a..0000000
--- a/tests/unit/auth/test_resources.py
+++ /dev/null
@@ -1,124 +0,0 @@
-"""Test resource-management functions"""
-import uuid
-
-import pytest
-
-from gn3.auth import db
-
-from gn3.auth.authorisation.groups import Group
-from gn3.auth.authorisation.errors import AuthorisationError
-from gn3.auth.authorisation.resources.models import (
-    Resource, user_resources, create_resource, ResourceCategory,
-    public_resources)
-
-from tests.unit.auth import conftest
-
-group = Group(uuid.UUID("9988c21d-f02f-4d45-8966-22c968ac2fbf"), "TheTestGroup",
-              {})
-resource_category = ResourceCategory(
-    uuid.UUID("fad071a3-2fc8-40b8-992b-cdefe7dcac79"), "mrna", "mRNA Dataset")
-create_resource_failure = {
-    "status": "error",
-    "message": "Unauthorised: Could not create resource"
-}
-
-
-def uuid_fn():
-    """Mock function for uuid"""
-    return uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b")
-
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected",
-    tuple(zip(
-        conftest.TEST_USERS[0:1],
-        (Resource(
-            group, uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"),
-            "test_resource", resource_category, False),))))
-def test_create_resource(mocker, fxtr_users_in_group, user, expected):
-    """Test that resource creation works as expected."""
-    mocker.patch("gn3.auth.authorisation.resources.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    conn, _group, _users = fxtr_users_in_group
-    resource = create_resource(
-        conn, "test_resource", resource_category, user, False)
-    assert resource == expected
-
-    with db.cursor(conn) as cursor:
-        # Cleanup
-        cursor.execute(
-            "DELETE FROM group_user_roles_on_resources WHERE resource_id=?",
-            (str(resource.resource_id),))
-        cursor.execute(
-            "DELETE FROM group_roles WHERE group_id=?",
-            (str(resource.group.group_id),))
-        cursor.execute(
-            "DELETE FROM resources WHERE resource_id=?",
-            (str(resource.resource_id),))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected",
-    tuple(zip(
-        conftest.TEST_USERS[1:],
-        (create_resource_failure, create_resource_failure,
-         create_resource_failure))))
-def test_create_resource_raises_for_unauthorised_users(
-        mocker, fxtr_users_in_group, user, expected):
-    """Test that resource creation works as expected."""
-    mocker.patch("gn3.auth.authorisation.resources.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    conn, _group, _users = fxtr_users_in_group
-    with pytest.raises(AuthorisationError):
-        assert create_resource(
-            conn, "test_resource", resource_category, user, False) == expected
-
-
-@pytest.mark.unit_test
-def test_public_resources(fxtr_resources):
-    """
-    GIVEN: some resources in the database
-    WHEN: public resources are requested
-    THEN: only list the resources that are public
-    """
-    conn, _res = fxtr_resources
-    assert sorted(
-        public_resources(conn),
-        key=lambda resource: resource.resource_id) == sorted(tuple(
-            res for res in
-            conftest.TEST_RESOURCES
-            if res.public), key=lambda resource: resource.resource_id)
-
-PUBLIC_RESOURCES = sorted(
-    {res.resource_id: res for res in conftest.TEST_RESOURCES_PUBLIC}.values(),
-    key=lambda resource: resource.resource_id)
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected",
-    tuple(zip(
-        conftest.TEST_USERS,
-        (sorted(
-            {res.resource_id: res for res in
-             (conftest.TEST_RESOURCES_GROUP_01 +
-              conftest.TEST_RESOURCES_PUBLIC)}.values(),
-            key=lambda resource: resource.resource_id),
-         sorted(
-             {res.resource_id: res for res in
-              ((conftest.TEST_RESOURCES_GROUP_01[1],) +
-               conftest.TEST_RESOURCES_PUBLIC)}.values()
-             , key=lambda resource: resource.resource_id),
-         PUBLIC_RESOURCES, PUBLIC_RESOURCES))))
-def test_user_resources(fxtr_group_user_roles, user, expected):
-    """
-    GIVEN: some resources in the database
-    WHEN: a particular user's resources are requested
-    THEN: list only the resources for which the user can access
-    """
-    conn, *_others = fxtr_group_user_roles
-    assert sorted(
-        {res.resource_id: res for res in user_resources(conn, user)
-         }.values(), key=lambda resource: resource.resource_id) == expected
diff --git a/tests/unit/auth/test_roles.py b/tests/unit/auth/test_roles.py
deleted file mode 100644
index 8e22bb5..0000000
--- a/tests/unit/auth/test_roles.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""Test functions dealing with group management."""
-import uuid
-
-import pytest
-
-from gn3.auth import db
-from gn3.auth.authorisation.privileges import Privilege
-from gn3.auth.authorisation.errors import AuthorisationError
-from gn3.auth.authorisation.roles.models import Role, user_roles, create_role
-
-from tests.unit.auth import conftest
-from tests.unit.auth.fixtures import TEST_USERS
-
-create_role_failure = {
-    "status": "error",
-    "message": "Unauthorised: Could not create role"
-}
-
-
-def uuid_fn():
-    """Mock function for uuid"""
-    return uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b")
-
-
-PRIVILEGES = (
-    Privilege("group:resource:view-resource",
-              "view a resource and use it in computations"),
-    Privilege("group:resource:edit-resource", "edit/update a resource"))
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected", tuple(zip(conftest.TEST_USERS[0:1], (
-        Role(uuid.UUID("d32611e3-07fc-4564-b56c-786c6db6de2b"), "a_test_role",
-             True, PRIVILEGES),))))
-def test_create_role(# pylint: disable=[too-many-arguments]
-        fxtr_app, auth_testdb_path, mocker, fxtr_users, user, expected):# pylint: disable=[unused-argument]
-    """
-    GIVEN: an authenticated user
-    WHEN: the user attempts to create a role
-    THEN: verify they are only able to create the role if they have the
-          appropriate privileges
-    """
-    mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        the_role = create_role(cursor, "a_test_role", PRIVILEGES)
-        assert the_role == expected
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected", tuple(zip(conftest.TEST_USERS[1:], (
-        create_role_failure, create_role_failure, create_role_failure))))
-def test_create_role_raises_exception_for_unauthorised_users(# pylint: disable=[too-many-arguments]
-        fxtr_app, auth_testdb_path, mocker, fxtr_users, user, expected):# pylint: disable=[unused-argument]
-    """
-    GIVEN: an authenticated user
-    WHEN: the user attempts to create a role
-    THEN: verify they are only able to create the role if they have the
-          appropriate privileges
-    """
-    mocker.patch("gn3.auth.authorisation.roles.models.uuid4", uuid_fn)
-    mocker.patch("gn3.auth.authorisation.checks.require_oauth.acquire",
-                 conftest.get_tokeniser(user))
-    with db.connection(auth_testdb_path) as conn, db.cursor(conn) as cursor:
-        with pytest.raises(AuthorisationError):
-            create_role(cursor, "a_test_role", PRIVILEGES)
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "user,expected",
-    (zip(TEST_USERS,
-         ((Role(
-             role_id=uuid.UUID('a0e67630-d502-4b9f-b23f-6805d0f30e30'),
-             role_name='group-leader', user_editable=False,
-             privileges=(
-                 Privilege(privilege_id='group:resource:create-resource',
-                           privilege_description='Create a resource object'),
-                 Privilege(privilege_id='group:resource:delete-resource',
-                           privilege_description='Delete a resource'),
-                 Privilege(privilege_id='group:resource:edit-resource',
-                           privilege_description='edit/update a resource'),
-                 Privilege(
-                     privilege_id='group:resource:view-resource',
-                     privilege_description=(
-                         'view a resource and use it in computations')),
-                 Privilege(privilege_id='group:role:create-role',
-                           privilege_description='Create a new role'),
-                 Privilege(privilege_id='group:role:delete-role',
-                           privilege_description='Delete an existing role'),
-                 Privilege(privilege_id='group:role:edit-role',
-                           privilege_description='edit/update an existing role'),
-                 Privilege(privilege_id='group:user:add-group-member',
-                           privilege_description='Add a user to a group'),
-                 Privilege(privilege_id='group:user:assign-role',
-                           privilege_description=(
-                               'Assign a role to an existing user')),
-                 Privilege(privilege_id='group:user:remove-group-member',
-                           privilege_description='Remove a user from a group'),
-                 Privilege(privilege_id='system:group:delete-group',
-                           privilege_description='Delete a group'),
-                 Privilege(privilege_id='system:group:edit-group',
-                           privilege_description='Edit the details of a group'),
-                 Privilege(
-                     privilege_id='system:group:transfer-group-leader',
-                     privilege_description=(
-                         'Transfer leadership of the group to some other '
-                         'member')),
-                 Privilege(privilege_id='system:group:view-group',
-                           privilege_description='View the details of a group'),
-                 Privilege(privilege_id='system:user:list',
-                           privilege_description='List users in the system'))),
-           Role(
-               role_id=uuid.UUID("ade7e6b0-ba9c-4b51-87d0-2af7fe39a347"),
-               role_name="group-creator", user_editable=False,
-               privileges=(
-                   Privilege(privilege_id='system:group:create-group',
-                             privilege_description = "Create a group"),))),
-          tuple(), tuple(), tuple()))))
-def test_user_roles(fxtr_group_user_roles, user, expected):
-    """
-    GIVEN: an authenticated user
-    WHEN: we request the user's privileges
-    THEN: return **ALL** the privileges attached to the user
-    """
-    conn, *_others = fxtr_group_user_roles
-    assert user_roles(conn, user) == expected
diff --git a/tests/unit/auth/test_token.py b/tests/unit/auth/test_token.py
deleted file mode 100644
index 76316ea..0000000
--- a/tests/unit/auth/test_token.py
+++ /dev/null
@@ -1,62 +0,0 @@
-"""Test the OAuth2 authorisation"""
-
-import pytest
-
-from gn3.auth import db
-
-SUCCESS_RESULT = {
-    "status_code": 200,
-    "result": {
-        "access_token": "123456ABCDE",
-        "expires_in": 864000,
-        "scope": "profile",
-        "token_type": "Bearer"}}
-
-USERNAME_PASSWORD_FAIL_RESULT = {
-    "status_code": 400,
-    "result": {
-        'error': 'invalid_request',
-        'error_description': 'Invalid "username" or "password" in request.'}}
-
-def gen_token(client, grant_type, user, scope): # pylint: disable=[unused-argument]
-    """Generate tokens for tests"""
-    return "123456ABCDE"
-
-@pytest.mark.unit_test
-@pytest.mark.parametrize(
-    "test_data,expected",
-    ((("group@lead.er", "password_for_user_001", 0), SUCCESS_RESULT),
-     (("group@mem.ber01", "password_for_user_002", 1), SUCCESS_RESULT),
-     (("group@mem.ber02", "password_for_user_003", 2), SUCCESS_RESULT),
-     (("unaff@iliated.user", "password_for_user_004", 3), SUCCESS_RESULT),
-     (("group@lead.er", "brrr", 0), USERNAME_PASSWORD_FAIL_RESULT),
-     (("group@mem.ber010", "password_for_user_002", 1), USERNAME_PASSWORD_FAIL_RESULT),
-     (("papa", "yada", 2), USERNAME_PASSWORD_FAIL_RESULT),
-     # (("unaff@iliated.user", "password_for_user_004", 1), USERNAME_PASSWORD_FAIL_RESULT)
-     ))
-def test_token(fxtr_app, fxtr_oauth2_clients, test_data, expected):
-    """
-    GIVEN: a registered oauth2 client, a user
-    WHEN: a token is requested via the 'password' grant
-    THEN: check that:
-      a) when email and password are valid, we get a token back
-      b) when either email or password or both are invalid, we get error message
-         back
-      c) TODO: when user tries to use wrong client, we get error message back
-    """
-    conn, oa2clients = fxtr_oauth2_clients
-    email, password, client_idx = test_data
-    data = {
-        "grant_type": "password", "scope": "profile nonexistent-scope",
-        "client_id": oa2clients[client_idx].client_id,
-        "client_secret": oa2clients[client_idx].client_secret,
-        "username": email, "password": password}
-
-    with fxtr_app.test_client() as client, db.cursor(conn) as cursor:
-        res = client.post("/api/oauth2/token", data=data)
-        # cleanup db
-        cursor.execute("DELETE FROM oauth2_tokens WHERE access_token=?",
-                       (gen_token(None, None, None, None),))
-    assert res.status_code == expected["status_code"]
-    for key in expected["result"]:
-        assert res.json[key] == expected["result"][key]