Skip to main content

Users and access

Added in 1.0.1

Chassis ships a users module that extends stock Django Admin: Users, Groups, admin sessions, login-attempt limits, the administrator profile, self password change, GeoIP country detection, and the Admin action journal.

It does not include product roles, MFA, or OIDC. Those stay in the application.

Global opt-out​

Added in 1.0.7

Projects with their own users runtime can disable Chassis automation globally:

CHASSIS_USERS_MODULE_ENABLED = False

This prevents automatic users Admin registration and pages, UserProfile synchronization, session-registry and audit middleware writes, and login-attempt limits. Chassis models and migrations remain available, so the database tables are still created. Remove the two users middlewares from the project configuration as well to make that configuration explicit.

What the site enables​

When include_users_module = True (the default), ChassisAdminSiteMixin:

  • registers Chassis UserAdmin / GroupAdmin for AUTH_USER_MODEL and Group
  • registers UserSession, LoginAttempt, LoginAttemptSchedule, AdminAuditLog
  • adds the personal Profile and Change password pages
  • adds a separate Create user changelist action and page
  • adds matching user-menu items
  • prepends the Users and groups sidebar section
  • uses ChassisAdminAuthenticationForm when login_form is unset
  • shows a GeoIP dashboard alert to superusers when the Country database is missing

Turn the module off on a site that must stay empty:

class CustomAdminSite(ChassisAdminSiteMixin, AdminSite):
include_users_module = False

The global CHASSIS_USERS_MODULE_ENABLED setting takes precedence over this per-site flag.

Keep the admins and pages but declare your own sidebar:

class CustomAdminSite(ChassisAdminSiteMixin, AdminSite):
include_users_sidebar = False
sidebar_items = (
# your sections, including auth.user / auth.group if you want them
)

Create and edit users​

Changed in 1.0.4

User creation follows the CPA flow instead of Django's stock add form. The standard Add button and direct add URL are disabled. A Create user action opens one Chassis page with username, password and confirmation, optional first name, last name and email, access flags, and groups. Empty group lists do not block creation. The page requires view, add, and change permissions for AUTH_USER_MODEL, django_chassis.manage_user_access, and its own django_chassis.view_user_creation permission. Active staff superusers bypass these checks as usual.

Added in 1.0.4

UserCreationAdminPage is attached automatically while the users module is enabled. On success it creates the user atomically, records the standard Django Admin addition log, and opens the new user's change form.

The change form uses the same CPA contract: account, personal data, access, and important-date sections. Password data and direct user_permissions are not rendered. Groups are assigned through Django's horizontal filtered selector. The creation form likewise never offers user_permissions, so non-superuser access is assigned through groups by default.

Added in 1.0.4

django_chassis.manage_user_access controls the is_active, is_staff, and groups fields for non-superusers. It never lets an operator edit their own access, edit a superuser's access, or change is_superuser; those operations remain superuser-only.

The default section is the stock Django “Users and groups” list plus Chassis access models. Product roles are not added:

  • auth.User
  • auth.Group
  • django_chassis.UserSession
  • django_chassis.LoginAttemptSchedule
  • django_chassis.LoginAttempt
  • django_chassis.AdminAuditLog

Override get_users_sidebar_section() if you need a different label or order.

The session changelist shows active sessions by default and provides explicit Inactive and All states. Its terminate action is available only for an active, non-current session when the administrator has the matching permission.

Changed in 1.0.4

The built-in session and Admin audit changelists render their records as full tables instead of a single combined text column. Session rows show the user, client, device, activity, expiry, and state; audit rows show the actor, action, result, request, response, IP, and browser. Related users are loaded with the list query.

Group permissions​

Changed in 1.0.4

The Group permission selector hides permissions whose content-type model name starts with historical. This removes the technical Historical* models created by django-simple-history from the default access flow.

Added in 1.0.4

Set SIMPLE_HISTORY_HIDE_HISTORY_MODEL_PERMISSIONS = False to show them again.

Middleware​

Add the session registry and Admin journal after authentication:

MIDDLEWARE = [
# ...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_chassis.middlewares.UserSessionMiddleware',
'django_chassis.middlewares.AdminAuditMiddleware',
]

Session and Admin audit rows store client IP via netaddr and browser/OS via device-detector (User-Agent plus Chromium Client Hints).

Both middlewares listen on CHASSIS_ADMIN_PATH_PREFIX (default /admin/).

UserSessionMiddleware registers the current staff session, refreshes last-seen at most once a minute, and logs the user out if the registry row is revoked or expired.

Database tables​

Changed in 1.0.1

The users module stores its data in chassis_user_profiles, chassis_user_sessions, chassis_login_attempt_schedules, chassis_login_attempts, and chassis_admin_audit_logs.

Settings​

SettingDefaultPurpose
CHASSIS_USERS_MODULE_ENABLEDTrueEnable the automatic users runtime globally
CHASSIS_LOGIN_ATTEMPT_LIMITS_ENABLEDTrueApply LoginAttemptSchedule delays
CHASSIS_ADMIN_PATH_PREFIX'/admin/'Path prefix for session and audit middleware
GEOIP_COUNTRY_DATABASE_PATHunsetAbsolute path to GeoLite2 Country .mmdb
SIMPLE_HISTORY_HIDE_HISTORY_MODEL_PERMISSIONSTrueHide Historical* model permissions from Group forms

An empty login-attempt schedule means no delays. Configure rows in Admin (first position must have delay_seconds=0).

GeoIP​

Changed in 1.0.1

geoip2 ships with the package. Point Django at a MaxMind Country file:

GEOIP_COUNTRY_DATABASE_PATH = '/var/lib/geoip/GeoLite2-Country.mmdb'

If the path or file is missing, country codes stay empty. Trusted proxy headers (CF-IPCountry, X-Country-Code, X-Geo-Country) are read only when the database file is available.

Profile and password​

AdminProfileAdminPage is a PersonalAdminPage: the current staff user sees account data, groups, last login/IP/country, the current session, and a timeline of their Admin audit rows. There is no MFA or product-role block.

SelfPasswordChangeAdminPage uses Django’s PasswordChangeForm (old password required), then logs the user out.

See also​

Custom authentication surfaces​

Added in 1.0.20

AuthenticationSurface lets an application use Chassis attempt limits, GeoIP, UserProfile and UserSession with its own templates and active nonstaff Django users. The default Admin login continues to require staff.

Each view has its own module: SurfaceLoginView in django_chassis.views.surface_login, SurfaceLogoutView in django_chassis.views.surface_logout, and SurfacePasswordChangeView in django_chassis.views.surface_password_change. The URL builder lives in django_chassis.views.authentication_surface_urls. All four are also exported from django_chassis.views.

from django_chassis.dto.authentication_surface import AuthenticationSurface
from django_chassis.views.authentication_surface_urls import authentication_surface_urls

surface = AuthenticationSurface(
name='operators',
login_url='/login/',
success_url='/workplace/',
login_template='operators/login.html',
password_template='operators/password.html',
url_prefixes=('/workplace/', '/profile/'),
staff_required=False,
lifetime_minutes=720,
expire_on_browser_close=True
)
urlpatterns = authentication_surface_urls(surface=surface)
CHASSIS_AUTHENTICATION_SURFACES = [surface]

Add django_chassis.middlewares.authentication_surface.AuthenticationSurfaceMiddleware after Django authentication middleware and Chassis UserSessionMiddleware. lifetime_minutes and expire_on_browser_close may be zero-argument callables, resolved on login to read application configuration without database reads at import. URLs are login, POST-only logout and profile/password. Django checks CSRF and validates next against the request host. The profile/session services remain available independently of Admin rendering.

The custom surface deadline is absolute and stored in shared Django session metadata; requests never extend it. Expiry denies only this surface and preserves an existing Admin-only registry/backend session. First login into a custom surface uses its browser-session/persistent cookie policy; entering from an existing registered Admin session preserves its cookie policy. Password change rotates the current key and invalidates other login sessions. A browser may restore session cookies after closing; the server deadline is the enforceable security boundary.

Call AuthenticationSurfaceService.is_expired from WebSocket consumers and verify active User plus UserSession registry on heartbeat. HTTP middleware alone cannot revoke an already-open socket. Applications retain ownership/business checks.

Changed in 1.0.20

chassis_page_classes accepts Sequence[type[AdminPage]], including lists. The existing default remains compatible.

Optional profile_provider, theme_context and audit_sink callbacks keep DTOs, appearance and safe auth-event capture independent of Admin templates. Providers receive the active User/request; the audit sink receives request and event (login, logout, password_change) without password data. Use AuthenticationSurfaceService.get_deadline to display the absolute custom-surface expiry, rather than the shared backend cookie expiry.