Authentication
Documentation / Authentication / Protection Middlewares & CurrentUser

Protection Middlewares & CurrentUser

Two approaches coexist. The manual pattern below is written directly in the handler — more explicit, full control over the redirect URL. To protect an entire route without touching it, login_required (via the RouterExt trait, at the urlpatterns!{} level) is still available — see Login Required.

use runique::prelude::*;

// Protect a route
async fn dashboard(mut request: Request) -> AppResult<Response> {
    if !is_authenticated(&request.session).await {
        return Ok(Redirect::to("/login").into_response());
    }
    // ...
}

// Redirect if already authenticated (login/register pages)
async fn login_page(mut request: Request) -> AppResult<Response> {
    if is_authenticated(&request.session).await {
        return Ok(Redirect::to("/").into_response());
    }
    // ...
}

`request.user` — the signed-in user

No middleware to add: Runique injects a CurrentUser into every request of a signed-in user, available as request.user.

The session only keeps who signed in. The account (active, rank, groups) is read from the database on every request — one lookup by primary key. A deleted or deactivated account therefore has its session closed on its next request: request.user is None and is_authenticated returns false, whatever made the change (admin, CLI, SQL, another instance).

use runique::prelude::*;

async fn profile(request: Request) -> impl IntoResponse {
    if let Some(user) = &request.user {
        println!("Logged in as: {}", user.username);
    }
}

CurrentUser

pub struct CurrentUser {
    pub id: Pk,                    // i32 by default, i64 with "big-pk", Uuid with "pk-uuid"
    pub username: String,
    pub rang: Rang,                // Lambda, Admin, Dirigeant, Superuser
    pub groupes: Vec<Groupe>,      // loaded by load_user_rights()
    pub droits: Vec<Permission>,   // an admin's own rights, loaded by load_user_rights()
}

Groups and their rights are not loaded by default: most pages never look at them. A view that checks a right loads them first — they are read from the database, and current_user is updated in the template context:

async fn articles(mut request: Request) -> AppResult<Response> {
    request.load_user_rights().await;
    let can_edit = request
        .user
        .as_ref()
        .is_some_and(|u| u.rights_on("articles").crud.can_update);
    // ...
}

Available Methods

// Rights on a resource, per rank: everything for a dirigeant or a superuser,
// an admin's own grid, the union of the groups for everyone else
user.rights_on("articles")              // → Rights { crud, columns }
user.rights_on("articles").crud.can_update

// May the user read a resource?
user.can_access_resource("articles")    // → bool

// Admin panel access: rank admin or above, or member of a staff group
user.can_access_admin()                 // → bool
user.is_staff()                         // member of a group that opens the admin

These methods read the groups and rights: call request.load_user_rights().await first.