Authentication

Full Example & AdminPanel

Full Example — Login / Logout

use runique::prelude::*;

// LoginForm — declared separately, .no_hash() is required on the password field
#[derive(Serialize, Debug, Clone)]
#[serde(transparent)]
pub struct LoginForm {
    pub form: Forms,
}

impl RuniqueForm for LoginForm {
    fn register_fields(form: &mut Forms) {
        form.field(&TextField::text("username").label("Username").required());
        form.field(&TextField::password("password").label("Password").no_hash().required());
    }
    impl_form_access!();
}

pub async fn login_post(mut request: Request) -> AppResult<Response> {
    let form: LoginForm = request.form();
    let validated = match ValidationForm::try_new(form, &request).await {
        Ok(validated) => validated,
        Err(form) => {
            context_update!(request => { "login_form" => &form });
            return request.render("login.html");
        }
    };

    let db = request.engine.db.clone();
    let username = validated.cleaned_string("username").unwrap_or_default();
    let password = validated.cleaned_string("password").unwrap_or_default();

    // 1. Find the user by username via search!
    let query = search!(users::Entity => Username eq username.trim());
    let user = query.first(&db).await.unwrap_or(None);

    if let Some(user) = user
        && user.is_active
        && verify(&password, &user.password)
    {
        // 2. Open the session — session-fixation-safe cycle_id() included
        auth_login(&request.session, &db, user.id).await.ok();
        return Ok(Redirect::to("/dashboard").into_response());
    }

    // Invalid credentials (generic message — don't distinguish unknown user / wrong password)
    let form = validated.into_form();
    context_update!(request => {
        "login_form" => &form,
        "messages" => flash_now!(error => "Invalid credentials"),
    });
    request.render("login.html")
}

pub async fn logout_view(mut request: Request) -> AppResult<Response> {
    logout(&request.session, None).await.ok();
    Ok(Redirect::to("/login").into_response())
}

Authentication for the AdminPanel

With the built-in User (zero config)

.with_admin(|a| a.auth(RuniqueAdminAuth::new()))

With a custom model

use runique::prelude::*;

// 1. Implement UserEntity on your entity
impl UserEntity for users::Entity {
    type Model = users::Model;

    async fn find_by_id(db: &DatabaseConnection, id: Pk) -> Option<Self::Model> {
        users::Entity::find_by_id(id).one(db).await.ok().flatten()
    }

    async fn find_by_username(db: &DatabaseConnection, username: &str) -> Option<Self::Model> {
        users::Entity::find()
            .filter(users::Column::Username.eq(username))
            .one(db)
            .await
            .ok()
            .flatten()
    }

    async fn find_by_email(db: &DatabaseConnection, email: &str) -> Option<Self::Model> {
        users::Entity::find()
            .filter(users::Column::Email.eq(email))
            .one(db)
            .await
            .ok()
            .flatten()
    }

    async fn update_password(db: &DatabaseConnection, email: &str, new_hash: &str) -> Result<(), sea_orm::DbErr> {
        // update password hash implementation
        todo!()
    }
}

// 2. Pass DefaultAdminAuth to the admin config
.with_admin(|a| a.auth(DefaultAdminAuth::<users::Entity>::new()))

To connect authentication to the admin panel, see also 11-Admin.md.