Rullst Nexus
rullst-nexus is a server-rendered administrative panel for models that
implement NexusModel, either manually or through #[derive(Nexus)]. The
derive infers primitive widgets and accepts explicit semantic metadata such as
kind = "textarea" and kind = "enum", options = "draft, published". Nexus
provides registered-model CRUD, search, pagination, semantically validated form
widgets, bounded selected-record delete/deactivate actions, telemetry, a
security view and an optional AI query page (the ai feature of
rullst-nexus, nexus-ai on the rullst facade; see
AI assistant). The current interface uses
server-side HTML and HTMX; Wasm islands,
drag-and-drop media management and automatic relationship discovery described by
older documentation were not implemented. They remain worthwhile separate
features, but must not be presented as current behavior.
Build a protected panel
Nexus fails closed: try_build() requires an explicit validated access policy.
The generated-app helper permits credential-free access only in debug builds and
only for a loopback peer proven by Axum ConnectInfo. Release builds require
NEXUS_ADMIN_USERNAME and a unique NEXUS_ADMIN_PASSWORD of at least 16
characters, each read from the process environment or else the working
directory’s .env (never loaded into the process environment). A malformed or
unreadable .env fails with NexusBuildError::InvalidDotenv.
#![allow(unused)]
fn main() {
use rullst_nexus::{Nexus, NexusAuthPolicy};
fn build() -> Result<axum::Router, Box<dyn std::error::Error>> {
let access = NexusAuthPolicy::local_development_or_basic_from_env()?;
let nexus = Nexus::new()
.with_auth_policy(access)
.with_brand("Application Admin")
// .register::<User>()
.try_build()?;
let app = axum::Router::new().nest("/nexus", nexus);
Ok(app)
}
}
That is the plain-Axum form. On a rullst::Router, as in the generated
starters, mount it with .nest_axum("/nexus", nexus): rullst::Router::nest
accepts only another rullst::Router, and there is no with_state (pass
shared state with .layer(Extension(state))). A release build without the
credentials makes local_development_or_basic_from_env() return
NexusBuildError::MissingCredential; the
Nexus guide
shows how to start the application without Nexus in that case, as the
Portfolio and ERP examples do.
An application with its own login can instead select
NexusAuthPolicy::application(authorizer): its NexusAuthorizer sees each
request and answers Allow (with the audit actor ID), SignIn (redirect to
the login page, or 401 with HX-Redirect for fragments and mutations),
Deny (403) or an error (503). The SaaS starter and the LMS example use it, through
local_development_or_application, for signed-in users whose users.is_admin
is true; see
Using the application’s sign-in.
The serving boundary must preserve the socket address, for example with Axum’s
into_make_service_with_connect_info::<SocketAddr>(). Basic Auth additionally
requires the application-owned NexusVerifiedTls capability inserted only after
validating a trusted TLS terminator, or an HTTPS report accepted by Core’s
TrustedProxyLayer from a listed proxy network with trust_forwarded_proto
enabled. The same layer (Server::trusted_proxies) gives each client behind the
proxy its own lockout bucket. Never derive either from an untrusted forwarded
header.
NexusAuthPolicy::protect_router can apply the same administrator boundary to
application-owned operational routes, as the ERP example does for its whole
back office: the dashboard and the inventory mutations.
Capability boundary
- Implemented: explicit model registration and a compile-tested derive;
server-rendered tables/forms;
bounded/validated registry metadata; parameterized and allowlisted SQL
identifiers; bound record values; server-side pair/byte limits and semantic
validation for Boolean, enum, JSON, number, date, e-mail and HTTP(S) URL
fields; CRUD, search, pagination, sort and batch operations, also for
composite primary keys; typed date/time writes that native date/time columns
accept; CSRF middleware; fail-closed loopback/Basic access and per-request
access through the application’s own sign-in; bounded Basic Auth failure throttling;
opt-in exact text-column tenant scope across every built-in read and mutation,
derived only from a trusted Core
TenantContext; and opt-in transaction-coupled minimized mutation audit with a fixed cross-database schema and bounded export API. - Batch boundary: at most 1,000 explicitly selected IDs; deactivation is
available only for a writable Boolean
is_activeoractivefield. - Application responsibility: identity, tenant membership and domain policy;
model/field authorization for global models and custom routes; database
privileges; trusted proxy and TLS configuration; secret rotation; immutable
external audit delivery, retention and backup; schema/type compatibility
(on PostgreSQL Nexus casts number, relation, Boolean, date and time writes,
but editable JSON and enums need text columns); and ownership rules beyond
the explicit tenant column. The built-in audit
table is transaction-coupled in the same database, not append-only or
tamper-evident, and records committed mutations rather than rejected
attempts. Multiline intent and
variants from an unrelated Rust enum require explicit
#[nexus]metadata; the struct derive does not invent them. - Not implemented: a generic
NexusLayer, automatic ORM schema reflection, automaticHasMany/BelongsTowidgets, full rich-media management and a shared-production authentication service. These ideas may be implemented when they have typed contracts and proportional tests.
For a complete model example and the local/release access flow, see Rullst Nexus: Explicit Admin CMS.