How do I fix a database configuration error when securing Django Admin with two factor authentication using custom user model and database?

93 views Asked by At

I am trying to add two factor authentication to the Django Admin Site as an extra layer of security using django-otp (version 1.2.2) and qrcode (version 7.4.2). Both have been installed as part of django-two-factor-auth (version 1.15.4). Furthermore, I have set up a custom user model in a custom Postgresql database. For the setup of the custom user model and database, I followed the tutorial Django Real Estate App Using Multiple Users & Databases by Brian Brkic (especially parts 1, 2, 3, and 10). For the implementation of the two factor authentication, I have started with the tutorial Towards Django Two Factor Authentication Integration by Very Academy, but continued with Django Two Factor Authentication Example because I thought I could avoid all the extra forms.

The migration to the custom database python manage.py migrate --database=users worked well and the Admin Site at localhost:8000/admin is presented properly, including the field for the OTP code. However, when I fill in the username, password and OTP code in the login form and hit send, I get the error

ImproperlyConfigured at /admin/login/

settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.

I came across this type of error in a different project and in that case it was caused by a missing app label in my router file. So, it can very well be that I am missing something here as well. Here is my router.py:

class AuthRouter:
    route_app_labels = {'user', 'admin', 'contenttypes', 'sessions', 'auth', 'otp_totp',}

    def db_for_read(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'users'
        return None
    
    def db_for_write(self, model, **hints):
        if model._meta.app_label in self.route_app_labels:
            return 'users'
        return None
    
    def allow_relation(self, obj1, obj2, **hints):
        if (
            obj1._meta.app_label in self.route_app_labels or
            obj2._meta.app_label in self.route_app_labels
        ):
            return True
        return None
    
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label in self.route_app_labels:
            return db == 'users'
        return None

An here is the snippet describing the database setup and the router setup in settings.py:

...
DATABASES = {
    'default': {},
    'users': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres-db',
        'USER': 'postgres-user',
        'PASSWORD': 'postgres-user-password'
    },
}

DATABASE_ROUTERS = ['user.router.AuthRouter',]
...

To follow my hunch that the error is caused by a migration issue, I made a clone of this project and replaced the postgresql database with the default sqlite database. Using the default database, I was able to implement 2FA successfully. Below you find the setup (in snippets) for the successful test. settings.py:

INSTALLED_APPS = [
    ...
    'user',
    'django_otp',
    'django_otp.plugins.otp_totp',
]

MIDDLEWARE = [
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django_otp.middleware.OTPMiddleware',
    ...
]
...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

and the project's urls.py:

from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
User = get_user_model()
from user.admin import UserAdmin
from django.urls import path, include
...
from django_otp.admin import OTPAdminSite
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.plugins.otp_totp.admin import TOTPDeviceAdmin

class OTPAdmin(OTPAdminSite):
    pass

admin_site = OTPAdmin(name='OTPAdmin')
admin_site.register(User,UserAdmin)
admin_site.register(Group)
admin_site.register(TOTPDevice, TOTPDeviceAdmin)

urlpatterns = [
    ...
    path('auth/user/', include('user.urls')),
    #path('admin/', admin.site.urls),
    path('admin/', admin_site.urls),
]

So, the only difference between the prostgresql-setup and the default-setup is indeed the database setup in settings.py.

I hope that someone has the right hint!

0

There are 0 answers