Compare commits
16 Commits
c9e4bb48e7
...
348c200f39
| Author | SHA1 | Date | |
|---|---|---|---|
| 348c200f39 | |||
| 042b9f3f60 | |||
| f57de93ac2 | |||
| 8e303466b0 | |||
| 5e4ff97b97 | |||
| 7eb014d5f9 | |||
| 753e52ff70 | |||
| 5a5913d5fe | |||
| 55be4935f1 | |||
| e71fad8546 | |||
| 75b70008e3 | |||
| 36399604fc | |||
| 281c94349a | |||
| 86e723f38c | |||
| 42e9ca5cfc | |||
| 361865574b |
1211
Cargo.lock
generated
1211
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
23
Cargo.toml
@@ -7,26 +7,26 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.13.3"
|
actix = "0.13.3"
|
||||||
actix-identity = "0.7.0"
|
actix-identity = "0.7.1"
|
||||||
actix-web = "4"
|
actix-web = "4.5.1"
|
||||||
actix-session = { version = "0.9.0", features = ["cookie-session"] }
|
actix-session = { version = "0.9.0", features = ["cookie-session"] }
|
||||||
actix-remote-ip = "0.1.0"
|
actix-remote-ip = "0.1.0"
|
||||||
clap = { version = "4.5.0", features = ["derive", "env"] }
|
clap = { version = "4.5.1", features = ["derive", "env"] }
|
||||||
include_dir = "0.7.3"
|
include_dir = "0.7.3"
|
||||||
log = "0.4.20"
|
log = "0.4.21"
|
||||||
serde_json = "1.0.113"
|
serde_json = "1.0.114"
|
||||||
serde_yaml = "0.9.30"
|
serde_yaml = "0.9.32"
|
||||||
env_logger = "0.11.2"
|
env_logger = "0.11.3"
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
bcrypt = "0.15.0"
|
bcrypt = "0.15.0"
|
||||||
uuid = { version = "1.6.1", features = ["v4"] }
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
askama = "0.12.1"
|
askama = "0.12.1"
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
base64 = "0.21.7"
|
base64 = "0.22.0"
|
||||||
jwt-simple = { version = "0.12.7", default-features=false, features=["pure-rust"] }
|
jwt-simple = { version = "0.12.9", default-features = false, features = ["pure-rust"] }
|
||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
lazy-regex = "3.1.0"
|
lazy-regex = "3.1.0"
|
||||||
@@ -39,3 +39,4 @@ light-openid = { version = "1.0.1", features=["crypto-wrapper"] }
|
|||||||
bincode = "2.0.0-rc.3"
|
bincode = "2.0.0-rc.3"
|
||||||
chrono = "0.4.34"
|
chrono = "0.4.34"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
mailchecker = "6.0.1"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM debian:bullseye-slim
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y libcurl4 \
|
&& apt-get install -y libcurl4 \
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::actors::users_actor::{DeleteUserRequest, FindUserByUsername, UsersAct
|
|||||||
use crate::data::action_logger::{Action, ActionLogger};
|
use crate::data::action_logger::{Action, ActionLogger};
|
||||||
use crate::data::current_user::CurrentUser;
|
use crate::data::current_user::CurrentUser;
|
||||||
use crate::data::user::UserID;
|
use crate::data::user::UserID;
|
||||||
|
use crate::utils::string_utils;
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct FindUserNameReq {
|
pub struct FindUserNameReq {
|
||||||
@@ -21,6 +22,10 @@ pub async fn find_username(
|
|||||||
req: web::Form<FindUserNameReq>,
|
req: web::Form<FindUserNameReq>,
|
||||||
users: web::Data<Addr<UsersActor>>,
|
users: web::Data<Addr<UsersActor>>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
|
if !string_utils::is_acceptable_login(&req.username) {
|
||||||
|
return HttpResponse::BadRequest().json("Invalid login!");
|
||||||
|
}
|
||||||
|
|
||||||
let res = users
|
let res = users
|
||||||
.send(FindUserByUsername(req.0.username))
|
.send(FindUserByUsername(req.0.username))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -15,19 +15,20 @@ use crate::data::client::{Client, ClientID, ClientManager};
|
|||||||
use crate::data::current_user::CurrentUser;
|
use crate::data::current_user::CurrentUser;
|
||||||
use crate::data::provider::{Provider, ProviderID, ProvidersManager};
|
use crate::data::provider::{Provider, ProviderID, ProvidersManager};
|
||||||
use crate::data::user::{GeneralSettings, GrantedClients, User, UserID};
|
use crate::data::user::{GeneralSettings, GrantedClients, User, UserID};
|
||||||
|
use crate::utils::string_utils;
|
||||||
use crate::utils::string_utils::rand_str;
|
use crate::utils::string_utils::rand_str;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/clients_list.html")]
|
#[template(path = "settings/clients_list.html")]
|
||||||
struct ClientsListTemplate<'a> {
|
struct ClientsListTemplate<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
clients: Vec<Client>,
|
clients: Vec<Client>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/providers_list.html")]
|
#[template(path = "settings/providers_list.html")]
|
||||||
struct ProvidersListTemplate<'a> {
|
struct ProvidersListTemplate<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
providers: Vec<Provider>,
|
providers: Vec<Provider>,
|
||||||
redirect_url: String,
|
redirect_url: String,
|
||||||
}
|
}
|
||||||
@@ -35,14 +36,14 @@ struct ProvidersListTemplate<'a> {
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/users_list.html")]
|
#[template(path = "settings/users_list.html")]
|
||||||
struct UsersListTemplate<'a> {
|
struct UsersListTemplate<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
users: Vec<User>,
|
users: Vec<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/edit_user.html")]
|
#[template(path = "settings/edit_user.html")]
|
||||||
struct EditUserTemplate<'a> {
|
struct EditUserTemplate<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
u: User,
|
u: User,
|
||||||
clients: Vec<Client>,
|
clients: Vec<Client>,
|
||||||
providers: Vec<Provider>,
|
providers: Vec<Provider>,
|
||||||
@@ -54,7 +55,7 @@ pub async fn clients_route(
|
|||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
ClientsListTemplate {
|
ClientsListTemplate {
|
||||||
_p: BaseSettingsPage::get("Clients list", &user, None, None),
|
p: BaseSettingsPage::get("Clients list", &user, None, None),
|
||||||
clients: clients.cloned(),
|
clients: clients.cloned(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
@@ -68,7 +69,7 @@ pub async fn providers_route(
|
|||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
ProvidersListTemplate {
|
ProvidersListTemplate {
|
||||||
_p: BaseSettingsPage::get("OpenID Providers list", &user, None, None),
|
p: BaseSettingsPage::get("OpenID Providers list", &user, None, None),
|
||||||
providers: providers.cloned(),
|
providers: providers.cloned(),
|
||||||
redirect_url: AppConfig::get().oidc_provider_redirect_url(),
|
redirect_url: AppConfig::get().oidc_provider_redirect_url(),
|
||||||
}
|
}
|
||||||
@@ -105,7 +106,16 @@ pub async fn users_route(
|
|||||||
let mut danger = None;
|
let mut danger = None;
|
||||||
let mut success = None;
|
let mut success = None;
|
||||||
|
|
||||||
if let Some(update) = update_query {
|
// Check update query for invalid input
|
||||||
|
if update_query
|
||||||
|
.as_ref()
|
||||||
|
.map(|l| string_utils::is_acceptable_login(&l.username))
|
||||||
|
== Some(false)
|
||||||
|
{
|
||||||
|
danger = Some("Invalid login provided, the modifications could not be saved!".to_string());
|
||||||
|
}
|
||||||
|
// Perform request (if any)
|
||||||
|
else if let Some(update) = update_query {
|
||||||
let edited_user: Option<User> = users
|
let edited_user: Option<User> = users
|
||||||
.send(users_actor::GetUserRequest(update.uid.clone()))
|
.send(users_actor::GetUserRequest(update.uid.clone()))
|
||||||
.await
|
.await
|
||||||
@@ -280,7 +290,7 @@ pub async fn users_route(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
UsersListTemplate {
|
UsersListTemplate {
|
||||||
_p: BaseSettingsPage::get("Users list", &admin, danger, success),
|
p: BaseSettingsPage::get("Users list", &admin, danger, success),
|
||||||
users,
|
users,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
@@ -306,7 +316,7 @@ pub async fn create_user(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
EditUserTemplate {
|
EditUserTemplate {
|
||||||
_p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
|
p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
|
||||||
u: user,
|
u: user,
|
||||||
clients: clients.cloned(),
|
clients: clients.cloned(),
|
||||||
providers: providers.cloned(),
|
providers: providers.cloned(),
|
||||||
@@ -336,7 +346,7 @@ pub async fn edit_user(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
EditUserTemplate {
|
EditUserTemplate {
|
||||||
_p: BaseSettingsPage::get(
|
p: BaseSettingsPage::get(
|
||||||
"Edit user account",
|
"Edit user account",
|
||||||
admin.deref(),
|
admin.deref(),
|
||||||
match edited_account.is_none() {
|
match edited_account.is_none() {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use crate::data::provider::{Provider, ProvidersManager};
|
|||||||
use crate::data::session_identity::{SessionIdentity, SessionStatus};
|
use crate::data::session_identity::{SessionIdentity, SessionStatus};
|
||||||
use crate::data::user::User;
|
use crate::data::user::User;
|
||||||
use crate::data::webauthn_manager::WebAuthManagerReq;
|
use crate::data::webauthn_manager::WebAuthManagerReq;
|
||||||
|
use crate::utils::string_utils;
|
||||||
|
|
||||||
pub struct BaseLoginPage<'a> {
|
pub struct BaseLoginPage<'a> {
|
||||||
pub danger: Option<String>,
|
pub danger: Option<String>,
|
||||||
@@ -30,7 +31,7 @@ pub struct BaseLoginPage<'a> {
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "login/login.html")]
|
#[template(path = "login/login.html")]
|
||||||
struct LoginTemplate<'a> {
|
struct LoginTemplate<'a> {
|
||||||
_p: BaseLoginPage<'a>,
|
p: BaseLoginPage<'a>,
|
||||||
login: String,
|
login: String,
|
||||||
providers: Vec<Provider>,
|
providers: Vec<Provider>,
|
||||||
}
|
}
|
||||||
@@ -38,27 +39,27 @@ struct LoginTemplate<'a> {
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "login/password_reset.html")]
|
#[template(path = "login/password_reset.html")]
|
||||||
struct PasswordResetTemplate<'a> {
|
struct PasswordResetTemplate<'a> {
|
||||||
_p: BaseLoginPage<'a>,
|
p: BaseLoginPage<'a>,
|
||||||
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<'a>,
|
||||||
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<'a> {
|
||||||
_p: BaseLoginPage<'a>,
|
p: BaseLoginPage<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "login/webauthn_input.html")]
|
#[template(path = "login/webauthn_input.html")]
|
||||||
struct LoginWithWebauthnTemplate<'a> {
|
struct LoginWithWebauthnTemplate<'a> {
|
||||||
_p: BaseLoginPage<'a>,
|
p: BaseLoginPage<'a>,
|
||||||
opaque_state: String,
|
opaque_state: String,
|
||||||
challenge_json: String,
|
challenge_json: String,
|
||||||
}
|
}
|
||||||
@@ -132,6 +133,16 @@ pub async fn login_route(
|
|||||||
query.redirect.get_encoded()
|
query.redirect.get_encoded()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
// Check if given login is not acceptable
|
||||||
|
else if req
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| string_utils::is_acceptable_login(&r.login))
|
||||||
|
== Some(false)
|
||||||
|
{
|
||||||
|
danger = Some(
|
||||||
|
"Given login could not be processed, because it has an invalid format!".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
// Try to authenticate user
|
// Try to authenticate user
|
||||||
else if let Some(req) = &req {
|
else if let Some(req) = &req {
|
||||||
login = req.login.clone();
|
login = req.login.clone();
|
||||||
@@ -199,7 +210,7 @@ pub async fn login_route(
|
|||||||
|
|
||||||
HttpResponse::Ok().content_type("text/html").body(
|
HttpResponse::Ok().content_type("text/html").body(
|
||||||
LoginTemplate {
|
LoginTemplate {
|
||||||
_p: BaseLoginPage {
|
p: BaseLoginPage {
|
||||||
page_title: "Login",
|
page_title: "Login",
|
||||||
danger,
|
danger,
|
||||||
success,
|
success,
|
||||||
@@ -273,7 +284,7 @@ pub async fn reset_password_route(
|
|||||||
|
|
||||||
HttpResponse::Ok().content_type("text/html").body(
|
HttpResponse::Ok().content_type("text/html").body(
|
||||||
PasswordResetTemplate {
|
PasswordResetTemplate {
|
||||||
_p: BaseLoginPage {
|
p: BaseLoginPage {
|
||||||
page_title: "Password reset",
|
page_title: "Password reset",
|
||||||
danger,
|
danger,
|
||||||
success: None,
|
success: None,
|
||||||
@@ -323,7 +334,7 @@ pub async fn choose_2fa_method(
|
|||||||
|
|
||||||
HttpResponse::Ok().content_type("text/html").body(
|
HttpResponse::Ok().content_type("text/html").body(
|
||||||
ChooseSecondFactorTemplate {
|
ChooseSecondFactorTemplate {
|
||||||
_p: BaseLoginPage {
|
p: BaseLoginPage {
|
||||||
page_title: "Two factor authentication",
|
page_title: "Two factor authentication",
|
||||||
danger: None,
|
danger: None,
|
||||||
success: None,
|
success: None,
|
||||||
@@ -408,7 +419,7 @@ pub async fn login_with_otp(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
LoginWithOTPTemplate {
|
LoginWithOTPTemplate {
|
||||||
_p: BaseLoginPage {
|
p: BaseLoginPage {
|
||||||
danger,
|
danger,
|
||||||
success: None,
|
success: None,
|
||||||
page_title: "Two-Factor Auth",
|
page_title: "Two-Factor Auth",
|
||||||
@@ -473,7 +484,7 @@ pub async fn login_with_webauthn(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
LoginWithWebauthnTemplate {
|
LoginWithWebauthnTemplate {
|
||||||
_p: BaseLoginPage {
|
p: BaseLoginPage {
|
||||||
danger: None,
|
danger: None,
|
||||||
success: None,
|
success: None,
|
||||||
page_title: "Two-Factor Auth",
|
page_title: "Two-Factor Auth",
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ 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<'a>,
|
||||||
message: &'a str,
|
message: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ProviderLoginError<'a> {
|
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,
|
danger: None,
|
||||||
success: None,
|
success: None,
|
||||||
page_title: "Upstream login",
|
page_title: "Upstream login",
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ impl<'a> BaseSettingsPage<'a> {
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/account_details.html")]
|
#[template(path = "settings/account_details.html")]
|
||||||
struct AccountDetailsPage<'a> {
|
struct AccountDetailsPage<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
remote_ip: String,
|
remote_ip: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/change_password.html")]
|
#[template(path = "settings/change_password.html")]
|
||||||
struct ChangePasswordPage<'a> {
|
struct ChangePasswordPage<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
min_pwd_len: usize,
|
min_pwd_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ pub async fn account_settings_details_route(user: CurrentUser, ip: RemoteIP) ->
|
|||||||
let user = user.into();
|
let user = user.into();
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
AccountDetailsPage {
|
AccountDetailsPage {
|
||||||
_p: BaseSettingsPage::get("Account details", &user, None, None),
|
p: BaseSettingsPage::get("Account details", &user, None, None),
|
||||||
remote_ip: ip.0.to_string(),
|
remote_ip: ip.0.to_string(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
@@ -145,7 +145,7 @@ pub async fn change_password_route(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
ChangePasswordPage {
|
ChangePasswordPage {
|
||||||
_p: BaseSettingsPage::get("Change password", &user, danger, success),
|
p: BaseSettingsPage::get("Change password", &user, danger, success),
|
||||||
min_pwd_len: MIN_PASS_LEN,
|
min_pwd_len: MIN_PASS_LEN,
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ use crate::data::webauthn_manager::WebAuthManagerReq;
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/two_factors_page.html")]
|
#[template(path = "settings/two_factors_page.html")]
|
||||||
struct TwoFactorsPage<'a> {
|
struct TwoFactorsPage<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
user: &'a User,
|
user: &'a User,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/add_2fa_totp_page.html")]
|
#[template(path = "settings/add_2fa_totp_page.html")]
|
||||||
struct AddTotpPage<'a> {
|
struct AddTotpPage<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
qr_code: String,
|
qr_code: String,
|
||||||
account_name: String,
|
account_name: String,
|
||||||
secret_key: String,
|
secret_key: String,
|
||||||
@@ -34,7 +34,7 @@ struct AddTotpPage<'a> {
|
|||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/add_webauthn_page.html")]
|
#[template(path = "settings/add_webauthn_page.html")]
|
||||||
struct AddWebauhtnPage<'a> {
|
struct AddWebauhtnPage<'a> {
|
||||||
_p: BaseSettingsPage<'a>,
|
p: BaseSettingsPage<'a>,
|
||||||
opaque_state: String,
|
opaque_state: String,
|
||||||
challenge_json: String,
|
challenge_json: String,
|
||||||
max_name_len: usize,
|
max_name_len: usize,
|
||||||
@@ -44,7 +44,7 @@ struct AddWebauhtnPage<'a> {
|
|||||||
pub async fn two_factors_route(user: CurrentUser) -> impl Responder {
|
pub async fn two_factors_route(user: CurrentUser) -> impl Responder {
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
TwoFactorsPage {
|
TwoFactorsPage {
|
||||||
_p: BaseSettingsPage::get("Two factor auth", &user, None, None),
|
p: BaseSettingsPage::get("Two factor auth", &user, None, None),
|
||||||
user: user.deref(),
|
user: user.deref(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
@@ -71,7 +71,7 @@ pub async fn add_totp_factor_route(user: CurrentUser) -> impl Responder {
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
AddTotpPage {
|
AddTotpPage {
|
||||||
_p: BaseSettingsPage::get("New authenticator app", &user, None, None),
|
p: BaseSettingsPage::get("New authenticator app", &user, None, None),
|
||||||
qr_code: BASE64_STANDARD.encode(qr_code),
|
qr_code: BASE64_STANDARD.encode(qr_code),
|
||||||
account_name: key.account_name(&user, AppConfig::get()),
|
account_name: key.account_name(&user, AppConfig::get()),
|
||||||
secret_key: key.get_secret(),
|
secret_key: key.get_secret(),
|
||||||
@@ -106,7 +106,7 @@ pub async fn add_webauthn_factor_route(
|
|||||||
|
|
||||||
HttpResponse::Ok().body(
|
HttpResponse::Ok().body(
|
||||||
AddWebauhtnPage {
|
AddWebauhtnPage {
|
||||||
_p: BaseSettingsPage::get("New security key", &user, None, None),
|
p: BaseSettingsPage::get("New security key", &user, None, None),
|
||||||
|
|
||||||
opaque_state: registration_request.opaque_state,
|
opaque_state: registration_request.opaque_state,
|
||||||
challenge_json: urlencoding::encode(&challenge_json).to_string(),
|
challenge_json: urlencoding::encode(&challenge_json).to_string(),
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ impl Eq for User {}
|
|||||||
impl Default for User {
|
impl Default for User {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
uid: UserID("".to_string()),
|
uid: UserID(uuid::Uuid::new_v4().to_string()),
|
||||||
first_name: "".to_string(),
|
first_name: "".to_string(),
|
||||||
last_name: "".to_string(),
|
last_name: "".to_string(),
|
||||||
username: "".to_string(),
|
username: "".to_string(),
|
||||||
|
|||||||
@@ -36,9 +36,14 @@ pub fn apply_env_vars(val: &str) -> String {
|
|||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check out whether a given login is acceptable or not
|
||||||
|
pub fn is_acceptable_login(login: &str) -> bool {
|
||||||
|
mailchecker::is_valid(login) || lazy_regex::regex!("^[a-zA-Z0-9-+]+$").is_match(login)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::utils::string_utils::apply_env_vars;
|
use crate::utils::string_utils::{apply_env_vars, is_acceptable_login};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
const VAR_ONE: &str = "VAR_ONE";
|
const VAR_ONE: &str = "VAR_ONE";
|
||||||
@@ -56,4 +61,12 @@ mod test {
|
|||||||
let src = format!("This is ${{{}}}", VAR_INVALID);
|
let src = format!("This is ${{{}}}", VAR_INVALID);
|
||||||
assert_eq!(src, apply_env_vars(&src));
|
assert_eq!(src, apply_env_vars(&src));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_acceptable_login() {
|
||||||
|
assert!(is_acceptable_login("admin"));
|
||||||
|
assert!(is_acceptable_login("someone@somewhere.fr"));
|
||||||
|
assert!(!is_acceptable_login("someone@somewhere.#fr"));
|
||||||
|
assert!(!is_acceptable_login("bad bad"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<!-- No indexing -->
|
<!-- No indexing -->
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
|
||||||
<title>{{ _p.app_name }} - {{ _p.page_title }}</title>
|
<title>{{ p.app_name }} - {{ p.page_title }}</title>
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
<link href="/assets/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous"/>
|
<link href="/assets/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous"/>
|
||||||
@@ -43,15 +43,15 @@
|
|||||||
|
|
||||||
<main class="form-signin">
|
<main class="form-signin">
|
||||||
|
|
||||||
<h1 class="h3 mb-3 fw-normal" style="margin-bottom: 2rem !important;">{{ _p.page_title }}</h1>
|
<h1 class="h3 mb-3 fw-normal" style="margin-bottom: 2rem !important;">{{ p.page_title }}</h1>
|
||||||
|
|
||||||
{% if let Some(danger) = _p.danger %}
|
{% if let Some(danger) = p.danger %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
{{ danger }}
|
{{ danger }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if let Some(success) = _p.success %}
|
{% if let Some(success) = p.success %}
|
||||||
<div class="alert alert-success" role="alert">
|
<div class="alert alert-success" role="alert">
|
||||||
{{ success }}
|
{{ success }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<p>You need to validate a second factor to complete your login.</p>
|
<p>You need to validate a second factor to complete your login.</p>
|
||||||
|
|
||||||
{% for factor in user.get_distinct_factors_types() %}
|
{% for factor in user.get_distinct_factors_types() %}
|
||||||
<a class="btn btn-primary btn-lg" href="{{ factor.login_url(_p.redirect_uri) }}" style="width: 100%; display: flex;">
|
<a class="btn btn-primary btn-lg" href="{{ factor.login_url(p.redirect_uri) }}" style="width: 100%; display: flex;">
|
||||||
<img src="{{ factor.type_image() }}" alt="Factor icon" style="margin-right: 1em;" />
|
<img src="{{ factor.type_image() }}" alt="Factor icon" style="margin-right: 1em;" />
|
||||||
<div style="text-align: left;">
|
<div style="text-align: left;">
|
||||||
{{ factor.type_str() }} <br/>
|
{{ factor.type_str() }} <br/>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<form action="/login?redirect={{ _p.redirect_uri.get_encoded() }}" method="post">
|
<form action="/login?redirect={{ p.redirect_uri.get_encoded() }}" method="post">
|
||||||
<div>
|
<div>
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input name="login" type="text" required class="form-control" id="floatingName" placeholder="unsername"
|
<input name="login" type="text" required class="form-control" id="floatingName" placeholder="unsername"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
{% if !providers.is_empty() %}
|
{% if !providers.is_empty() %}
|
||||||
<div id="providers">
|
<div id="providers">
|
||||||
{% for prov in providers %}
|
{% for prov in providers %}
|
||||||
<a class="btn btn-secondary btn-lg provider-button" href="{{ prov.login_url(_p.redirect_uri) }}">
|
<a class="btn btn-secondary btn-lg provider-button" href="{{ prov.login_url(p.redirect_uri) }}">
|
||||||
<img src="{{ prov.logo_url() }}" alt="Provider icon"/>
|
<img src="{{ prov.logo_url() }}" alt="Provider icon"/>
|
||||||
<div style="text-align: left;">
|
<div style="text-align: left;">
|
||||||
Login using {{ prov.name }} <br/>
|
Login using {{ prov.name }} <br/>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div style="margin-top: 10px;">
|
<div style="margin-top: 10px;">
|
||||||
<a href="/2fa_auth?force_display=true&redirect={{ _p.redirect_uri.get_encoded() }}">Sign in using another factor</a><br/>
|
<a href="/2fa_auth?force_display=true&redirect={{ p.redirect_uri.get_encoded() }}">Sign in using another factor</a><br/>
|
||||||
<a href="/logout">Sign out</a>
|
<a href="/logout">Sign out</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base_login_page.html" %}
|
{% extends "base_login_page.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="/reset_password?redirect={{ _p.redirect_uri.get_encoded() }}" method="post" id="reset_password_form">
|
<form action="/reset_password?redirect={{ p.redirect_uri.get_encoded() }}" method="post" id="reset_password_form">
|
||||||
<div>
|
<div>
|
||||||
<p>You need to configure a new password:</p>
|
<p>You need to configure a new password:</p>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<p style="margin-top: 10px; text-align: justify;">{{ message }}</p>
|
<p style="margin-top: 10px; text-align: justify;">{{ message }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="/login?redirect={{ _p.redirect_uri.get_encoded() }}">Go back to login</a>
|
<a href="/login?redirect={{ p.redirect_uri.get_encoded() }}">Go back to login</a>
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -12,13 +12,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 10px;">
|
<div style="margin-top: 10px;">
|
||||||
<a href="/2fa_auth?force_display=true&redirect={{ _p.redirect_uri.get_encoded() }}">Sign in using another factor</a><br/>
|
<a href="/2fa_auth?force_display=true&redirect={{ p.redirect_uri.get_encoded() }}">Sign in using another factor</a><br/>
|
||||||
<a href="/logout">Sign out</a>
|
<a href="/logout">Sign out</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/assets/js/base64_lib.js"></script>
|
<script src="/assets/js/base64_lib.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const REDIRECT_URI = decodeURIComponent("{{ _p.redirect_uri.get_encoded() }}");
|
const REDIRECT_URI = decodeURIComponent("{{ p.redirect_uri.get_encoded() }}");
|
||||||
const OPAQUE_STATE = "{{ opaque_state }}";
|
const OPAQUE_STATE = "{{ opaque_state }}";
|
||||||
const AUTH_CHALLENGE = JSON.parse(decodeURIComponent("{{ challenge_json }}"));
|
const AUTH_CHALLENGE = JSON.parse(decodeURIComponent("{{ challenge_json }}"));
|
||||||
// Decode data
|
// Decode data
|
||||||
|
|||||||
@@ -5,27 +5,27 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">User ID</th>
|
<th scope="row">User ID</th>
|
||||||
<td>{{ _p.user.uid.0 }}</td>
|
<td>{{ p.user.uid.0 }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">First name</th>
|
<th scope="row">First name</th>
|
||||||
<td>{{ _p.user.first_name }}</td>
|
<td>{{ p.user.first_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Last name</th>
|
<th scope="row">Last name</th>
|
||||||
<td>{{ _p.user.last_name }}</td>
|
<td>{{ p.user.last_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Username</th>
|
<th scope="row">Username</th>
|
||||||
<td>{{ _p.user.username }}</td>
|
<td>{{ p.user.username }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Email</th>
|
<th scope="row">Email</th>
|
||||||
<td>{{ _p.user.email }}</td>
|
<td>{{ p.user.email }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Account type</th>
|
<th scope="row">Account type</th>
|
||||||
<td>{% if _p.user.admin %}Admin{% else %}Regular user{% endif %}</td>
|
<td>{% if p.user.admin %}Admin{% else %}Regular user{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{{ _p.page_title }} - {{ _p.app_name }}</title>
|
<title>{{ p.page_title }} - {{ p.app_name }}</title>
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
<link href="/assets/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous"/>
|
<link href="/assets/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous"/>
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="d-flex flex-column flex-shrink-0 p-3 bg-light" style="width: 280px;">
|
<div class="d-flex flex-column flex-shrink-0 p-3 bg-light" style="width: 280px;">
|
||||||
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none">
|
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none">
|
||||||
<span class="fs-4">{{ _p.app_name }}</span>
|
<span class="fs-4">{{ p.app_name }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% if _p.user.admin %}
|
{% if p.user.admin %}
|
||||||
<span>Version {{ _p.version }}</span>
|
<span>Version {{ p.version }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<hr>
|
<hr>
|
||||||
<ul class="nav nav-pills flex-column mb-auto">
|
<ul class="nav nav-pills flex-column mb-auto">
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
Account details
|
Account details
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if _p.user.allow_local_login %}
|
{% if p.user.allow_local_login %}
|
||||||
<li>
|
<li>
|
||||||
<a href="/settings/change_password" class="nav-link link-dark">
|
<a href="/settings/change_password" class="nav-link link-dark">
|
||||||
Change password
|
Change password
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if _p.user.admin %}
|
{% if p.user.admin %}
|
||||||
<hr/>
|
<hr/>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/clients" class="nav-link link-dark">
|
<a href="/admin/clients" class="nav-link link-dark">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<a href="#" class="d-flex align-items-center link-dark text-decoration-none dropdown-toggle" id="dropdownUser"
|
<a href="#" class="d-flex align-items-center link-dark text-decoration-none dropdown-toggle" id="dropdownUser"
|
||||||
data-bs-toggle="dropdown" aria-expanded="false">
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<img src="/assets/img/account.png" alt="" width="32" height="32" class="rounded-circle me-2">
|
<img src="/assets/img/account.png" alt="" width="32" height="32" class="rounded-circle me-2">
|
||||||
<strong>{{ _p.user.username }}</strong>
|
<strong>{{ p.user.username }}</strong>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu text-small shadow" aria-labelledby="dropdownUser">
|
<ul class="dropdown-menu text-small shadow" aria-labelledby="dropdownUser">
|
||||||
<li><a class="dropdown-item" href="/logout">Sign out</a></li>
|
<li><a class="dropdown-item" href="/logout">Sign out</a></li>
|
||||||
@@ -70,14 +70,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="page_body" style="flex: 1">
|
<div class="page_body" style="flex: 1">
|
||||||
{% if let Some(msg) = _p.danger_message %}
|
{% if let Some(msg) = p.danger_message %}
|
||||||
<div class="alert alert-danger">{{ msg }}</div>
|
<div class="alert alert-danger">{{ msg }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if let Some(msg) = _p.success_message %}
|
{% if let Some(msg) = p.success_message %}
|
||||||
<div class="alert alert-success">{{ msg }}</div>
|
<div class="alert alert-success">{{ msg }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h2 class="bd-title mt-0" style="margin-bottom: 40px;">{{ _p.page_title }}</h2>
|
<h2 class="bd-title mt-0" style="margin-bottom: 40px;">{{ p.page_title }}</h2>
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
TO_REPLACE
|
TO_REPLACE
|
||||||
@@ -92,8 +92,8 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% if _p.ip_location_api.is_some() %}
|
{% if p.ip_location_api.is_some() %}
|
||||||
<script>const IP_LOCATION_API = "{{ _p.ip_location_api.unwrap() }}"</script>
|
<script>const IP_LOCATION_API = "{{ p.ip_location_api.unwrap() }}"</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script src="/assets/js/ip_location_service.js"></script>
|
<script src="/assets/js/ip_location_service.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary mt-4" value="{{ _p.page_title }}">
|
<input type="submit" class="btn btn-primary mt-4" value="{{ p.page_title }}">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user