Session Helpers
Import
use runique::prelude::*;
Login
login — open the session
Two steps: authenticate_user checks the credentials (password, active and activated account), then login records the account in the session. login renews the session id and the CSRF token, and writes only the id: the account is read from the database on every request (request.user).
match authenticate_user(&db, &username, &password).await {
Some(user) => login(&session, &user, None, false).await?,
None => { /* invalid credentials: generic message */ }
}
Parameters: the account (&impl RuniqueUser), multi-device persistence (Option<&RuniqueSessionStore>; the default store already saves signed-in sessions to the database) and exclusive login.
login checks by itself that the account may sign in (can_sign_in(): active and activated), whatever path loaded it. Otherwise nothing is written and it returns LoginError::CannotSignIn:
match login(&session, &user, None, false).await {
Ok(()) => { /* signed in */ }
Err(LoginError::CannotSignIn) => { /* inactive, or not activated yet */ }
Err(LoginError::Session(e)) => { /* the session store failed */ }
}
An account loaded another way (after a registration, OAuth, a magic link) signs in with the same
login. It must come from the server — an account just created, an identity verified — never from an id received in the request.
Activate, then sign in
BuiltinUserEntity::activate_account activates a pending account (is_active + activated_at) and returns the updated account, ready for login. An account activated before, or deactivated since by the staff, is left untouched (None): reactivation stays the staff's decision.
if let Some(user) = BuiltinUserEntity::activate_account(&db, id).await? {
login(&session, &user, None, false).await?;
}
Exclusive login
To allow only one active session per user at a time, pass exclusive: true:
login(&session, &user, Some(&store), true).await?;
Or enable globally via the builder:
RuniqueApp::builder(config)
.middleware(|m| m.with_exclusive_login(true))
Logout
logout(&session, None).await?;
// With DB session deletion (multi-device)
logout(&session, Some(&store)).await?;
logout() clears the whole session (flash messages included) and gives it a new id: a flash message meant for the next page is added after the call.
When the deletion fails (database down), logout() returns the error and the session stays whole, cookie included: the user can try again. Don't show "signed out" then. Likewise, login as another account first closes the previous session; when it can't, the new sign-in is refused (LoginError::Session).
Revoking sessions
logout() only ends the current session. To invalidate all of a user's sessions ("log out everywhere", account compromise, password change), you go through the stores.
The built-in password reset (with_password_reset) already does it once the new password is saved. In your own handler (password change from a profile page, compromised account…), one method is enough:
request.engine.close_user_sessions(user_id).await?;
It deletes the account's sessions from the database (eihwaz_sessions), then their copies in memory. The database is the authority: every request of a signed-in session checks its row again, so a session deleted from the database is refused everywhere, another instance included. When the database fails, nothing else is touched and the error is returned: the sessions are still alive, don't present the operation as done.
Custom store: a store plugged in with
with_session_store()(Redis…) can't be searched by user. Its sessions close anyway:close_user_sessionsand a completed reset increasesession_versionon the account, and a session opened under an older version ends at its next request.
To revoke only the other devices while keeping the current session, use invalidate_other_sessions(user_id, &cookie_id) on the DB store — this is exactly what exclusive login does.
Checks
// Is the user authenticated?
if is_authenticated(&session).await {
// ...
}
// The signed-in account, read from the database on every request (like Django's `request.user`)
if let Some(user) = &request.user {
let user_id = user.id; // Pk = i32/i64/Uuid depending on the active feature
let username = &user.username;
}