Users and access
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.7Projects 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/GroupAdminforAUTH_USER_MODELandGroup - 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
ChassisAdminAuthenticationFormwhenlogin_formis 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.4User 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.
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.
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.
Sidebar
The default section is the stock Django “Users and groups” list plus Chassis access models. Product roles are not added:
auth.Userauth.Groupdjango_chassis.UserSessiondjango_chassis.LoginAttemptScheduledjango_chassis.LoginAttemptdjango_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.4The 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.4The 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.
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.1The 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
| Setting | Default | Purpose |
|---|---|---|
CHASSIS_USERS_MODULE_ENABLED | True | Enable the automatic users runtime globally |
CHASSIS_LOGIN_ATTEMPT_LIMITS_ENABLED | True | Apply LoginAttemptSchedule delays |
CHASSIS_ADMIN_PATH_PREFIX | '/admin/' | Path prefix for session and audit middleware |
GEOIP_COUNTRY_DATABASE_PATH | unset | Absolute path to GeoLite2 Country .mmdb |
SIMPLE_HISTORY_HIDE_HISTORY_MODEL_PERMISSIONS | True | Hide 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.1geoip2 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.20AuthenticationSurface 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.
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.