✨ Añade pagetop-user, con autenticación y RBAC
Identidad de usuario, sesiones en base de datos, roles y permisos (RBAC) con "authenticated" implícito, contraseñas con Argon2id, tokens de un solo uso para verificación de email y restablecimiento de contraseña, y la UI de administración completa (usuarios, roles, permisos) sobre pagetop-admin. Incluye datos de demostración opcionales tras la feature `demo-data`.
This commit is contained in:
parent
043873a954
commit
def0513246
57 changed files with 7409 additions and 0 deletions
112
extensions/pagetop-user/src/component/login_form.rs
Normal file
112
extensions/pagetop-user/src/component/login_form.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
//! Componente de formulario de inicio de sesión.
|
||||
|
||||
use pagetop::prelude::*;
|
||||
|
||||
use crate::component::error_banner;
|
||||
use crate::config::SETTINGS;
|
||||
use crate::{LOCALES_USER, LOGIN_PATH, PASSWORD_RESET_PATH, REGISTER_PATH};
|
||||
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct LoginForm {
|
||||
error: Option<Lc>,
|
||||
/// Página a la que volver tras iniciar sesión.
|
||||
waypoint: Waypoint,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for LoginForm {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let action = self.waypoint().append_to(cx.route(LOGIN_PATH));
|
||||
let strict = SETTINGS.login_strict;
|
||||
|
||||
// En modo estricto se dificulta que el navegador recuerde o autorrellene las
|
||||
// credenciales: nombres de campo neutros y campos `Field::strict_text()`/
|
||||
// `Field::strict_password()`, que ya incluyen el resto de medidas (ver su documentación).
|
||||
let username = if strict {
|
||||
form::input::Field::strict_text()
|
||||
} else {
|
||||
form::input::Field::text().with_autocomplete(Some(form::Autocomplete::username()))
|
||||
}
|
||||
.with_name(if strict { "ident" } else { "username" })
|
||||
.with_label(Lc::t("field-username", &LOCALES_USER))
|
||||
.with_autofocus(true)
|
||||
.with_required(true);
|
||||
|
||||
let password = if strict {
|
||||
form::input::Field::strict_password()
|
||||
} else {
|
||||
form::input::Field::password()
|
||||
.with_autocomplete(Some(form::Autocomplete::current_password()))
|
||||
}
|
||||
.with_name(if strict { "token" } else { "password" })
|
||||
.with_label(Lc::t("field-password", &LOCALES_USER))
|
||||
.with_required(true);
|
||||
|
||||
let mut form = Form::new()
|
||||
.with_id("user-login-form")
|
||||
.with_action(action)
|
||||
.with_method(form::Method::Post)
|
||||
.with_child(error_banner(self.error().cloned()))
|
||||
.with_child(username)
|
||||
.with_child(password)
|
||||
.with_child(
|
||||
form::Checkbox::check()
|
||||
.with_name("remember")
|
||||
.with_label(Lc::t("field-remember-me", &LOCALES_USER)),
|
||||
)
|
||||
.with_child(Button::submit(Lc::t("btn-login", &LOCALES_USER)))
|
||||
.with_child(links(SETTINGS.allow_registration));
|
||||
|
||||
if strict {
|
||||
form = form
|
||||
.with_prop(PropsOp::set("autocomplete", "off"))
|
||||
.with_prop(PropsOp::set("novalidate", "novalidate"));
|
||||
}
|
||||
|
||||
let form = form.render(cx).await;
|
||||
Ok(html! {
|
||||
div.user-login-page {
|
||||
div.user-login-card {
|
||||
(form)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn links(allow_registration: bool) -> Html {
|
||||
Html::with(move |cx| {
|
||||
html! {
|
||||
@if allow_registration {
|
||||
p.user-register-link {
|
||||
a href=(cx.route(REGISTER_PATH)) {
|
||||
(Lc::t("link-register", &LOCALES_USER).using(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
p.user-reset-link {
|
||||
a href=(cx.route(PASSWORD_RESET_PATH)) {
|
||||
(Lc::t("link-forgot-password", &LOCALES_USER).using(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
impl LoginForm {
|
||||
#[builder_fn]
|
||||
pub fn with_error(mut self, error: impl Into<Option<Lc>>) -> Self {
|
||||
self.error = error.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_waypoint(mut self, waypoint: impl Into<Waypoint>) -> Self {
|
||||
self.waypoint = waypoint.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue