Can customize login background image
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-28 18:05:35 +01:00
parent 2a729d4153
commit ffd93c5435
5 changed files with 50 additions and 33 deletions

View File

@@ -14,7 +14,6 @@ body {
/* background */ /* background */
@media screen and (min-width: 767px) { @media screen and (min-width: 767px) {
.bg-login { .bg-login {
background-image: url(/assets/img/forest.jpg);
width: 100%; width: 100%;
height: 100%; height: 100%;
position: fixed; position: fixed;

View File

@@ -13,6 +13,7 @@ use crate::controllers::base_controller::{
build_fatal_error_page, redirect_user, redirect_user_for_login, build_fatal_error_page, redirect_user, redirect_user_for_login,
}; };
use crate::data::action_logger::{Action, ActionLogger}; use crate::data::action_logger::{Action, ActionLogger};
use crate::data::app_config::AppConfig;
use crate::data::force_2fa_auth::Force2FAAuth; use crate::data::force_2fa_auth::Force2FAAuth;
use crate::data::login_redirect::{LoginRedirect, get_2fa_url}; use crate::data::login_redirect::{LoginRedirect, get_2fa_url};
use crate::data::provider::{Provider, ProvidersManager}; use crate::data::provider::{Provider, ProvidersManager};
@@ -21,46 +22,60 @@ use crate::data::user::User;
use crate::data::webauthn_manager::WebAuthManagerReq; use crate::data::webauthn_manager::WebAuthManagerReq;
use crate::utils::string_utils; use crate::utils::string_utils;
pub struct BaseLoginPage<'a> { pub struct BaseLoginPage {
pub danger: Option<String>, pub danger: Option<String>,
pub success: Option<String>, pub success: Option<String>,
pub background_image: &'static str,
pub page_title: &'static str, pub page_title: &'static str,
pub app_name: &'static str, pub app_name: &'static str,
pub redirect_uri: &'a LoginRedirect, pub redirect_uri: LoginRedirect,
}
impl Default for BaseLoginPage {
fn default() -> Self {
Self {
page_title: "Login",
danger: None,
success: None,
background_image: &AppConfig::get().login_background_image,
app_name: APP_NAME,
redirect_uri: LoginRedirect::default(),
}
}
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/login.html")] #[template(path = "login/login.html")]
struct LoginTemplate<'a> { struct LoginTemplate {
p: BaseLoginPage<'a>, p: BaseLoginPage,
login: String, login: String,
providers: Vec<Provider>, providers: Vec<Provider>,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/password_reset.html")] #[template(path = "login/password_reset.html")]
struct PasswordResetTemplate<'a> { struct PasswordResetTemplate {
p: BaseLoginPage<'a>, p: BaseLoginPage,
min_pass_len: usize, min_pass_len: usize,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/choose_second_factor.html")] #[template(path = "login/choose_second_factor.html")]
struct ChooseSecondFactorTemplate<'a> { struct ChooseSecondFactorTemplate<'a> {
p: BaseLoginPage<'a>, p: BaseLoginPage,
user: &'a User, user: &'a User,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/otp_input.html")] #[template(path = "login/otp_input.html")]
struct LoginWithOTPTemplate<'a> { struct LoginWithOTPTemplate {
p: BaseLoginPage<'a>, p: BaseLoginPage,
} }
#[derive(Template)] #[derive(Template)]
#[template(path = "login/webauthn_input.html")] #[template(path = "login/webauthn_input.html")]
struct LoginWithWebauthnTemplate<'a> { struct LoginWithWebauthnTemplate {
p: BaseLoginPage<'a>, p: BaseLoginPage,
opaque_state: String, opaque_state: String,
challenge_json: String, challenge_json: String,
} }
@@ -216,8 +231,8 @@ pub async fn login_route(
page_title: "Login", page_title: "Login",
danger, danger,
success, success,
app_name: APP_NAME, redirect_uri: query.0.redirect,
redirect_uri: &query.redirect, ..Default::default()
}, },
login, login,
providers: providers.cloned(), providers: providers.cloned(),
@@ -289,9 +304,8 @@ pub async fn reset_password_route(
p: BaseLoginPage { p: BaseLoginPage {
page_title: "Password reset", page_title: "Password reset",
danger, danger,
success: None, redirect_uri: query.0.redirect,
app_name: APP_NAME, ..Default::default()
redirect_uri: &query.redirect,
}, },
min_pass_len: MIN_PASS_LEN, min_pass_len: MIN_PASS_LEN,
} }
@@ -339,10 +353,8 @@ pub async fn choose_2fa_method(
ChooseSecondFactorTemplate { ChooseSecondFactorTemplate {
p: BaseLoginPage { p: BaseLoginPage {
page_title: "Two factor authentication", page_title: "Two factor authentication",
danger: None, redirect_uri: query.0.redirect,
success: None, ..Default::default()
app_name: APP_NAME,
redirect_uri: &query.redirect,
}, },
user: &user, user: &user,
} }
@@ -430,8 +442,8 @@ pub async fn login_with_otp(
danger, danger,
success: None, success: None,
page_title: "Two-Factor Auth", page_title: "Two-Factor Auth",
app_name: APP_NAME, redirect_uri: query.0.redirect,
redirect_uri: &query.redirect, ..Default::default()
}, },
} }
.render() .render()
@@ -493,11 +505,9 @@ pub async fn login_with_webauthn(
HttpResponse::Ok().body( HttpResponse::Ok().body(
LoginWithWebauthnTemplate { LoginWithWebauthnTemplate {
p: BaseLoginPage { p: BaseLoginPage {
danger: None,
success: None,
page_title: "Two-Factor Auth", page_title: "Two-Factor Auth",
app_name: APP_NAME, redirect_uri: query.0.redirect,
redirect_uri: &query.redirect, ..Default::default()
}, },
opaque_state: challenge.opaque_state, opaque_state: challenge.opaque_state,
challenge_json: urlencoding::encode(&challenge_json).to_string(), challenge_json: urlencoding::encode(&challenge_json).to_string(),

View File

@@ -10,7 +10,7 @@ use crate::actors::bruteforce_actor::BruteForceActor;
use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor}; use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor};
use crate::actors::users_actor::{LoginResult, UsersActor}; use crate::actors::users_actor::{LoginResult, UsersActor};
use crate::actors::{bruteforce_actor, providers_states_actor, users_actor}; use crate::actors::{bruteforce_actor, providers_states_actor, users_actor};
use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS}; use crate::constants::MAX_FAILED_LOGIN_ATTEMPTS;
use crate::controllers::base_controller::{build_fatal_error_page, redirect_user}; use crate::controllers::base_controller::{build_fatal_error_page, redirect_user};
use crate::controllers::login_controller::BaseLoginPage; use crate::controllers::login_controller::BaseLoginPage;
use crate::data::action_logger::{Action, ActionLogger}; use crate::data::action_logger::{Action, ActionLogger};
@@ -22,7 +22,7 @@ use crate::data::session_identity::{SessionIdentity, SessionStatus};
#[derive(askama::Template)] #[derive(askama::Template)]
#[template(path = "login/prov_login_error.html")] #[template(path = "login/prov_login_error.html")]
struct ProviderLoginError<'a> { struct ProviderLoginError<'a> {
p: BaseLoginPage<'a>, p: BaseLoginPage,
message: &'a str, message: &'a str,
} }
@@ -30,11 +30,9 @@ impl<'a> ProviderLoginError<'a> {
pub fn get(message: &'a str, redirect_uri: &'a LoginRedirect) -> HttpResponse { pub fn get(message: &'a str, redirect_uri: &'a LoginRedirect) -> HttpResponse {
let body = Self { let body = Self {
p: BaseLoginPage { p: BaseLoginPage {
danger: None,
success: None,
page_title: "Upstream login", page_title: "Upstream login",
app_name: APP_NAME, redirect_uri: redirect_uri.clone(),
redirect_uri, ..Default::default()
}, },
message, message,
} }

View File

@@ -58,6 +58,10 @@ pub struct AppConfig {
/// Action logger output format /// Action logger output format
#[arg(long, env, default_value_t, value_enum)] #[arg(long, env, default_value_t, value_enum)]
pub action_logger_format: ActionLoggerFormat, pub action_logger_format: ActionLoggerFormat,
/// Login background image
#[arg(long, env, default_value = "/assets/img/forest.jpg")]
pub login_background_image: String,
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {

View File

@@ -30,6 +30,12 @@
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
@media screen and (min-width: 767px) {
.bg-login {
background-image: url({{ p.background_image }});
}
}
</style> </style>