Can request new user password on login
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
use actix::{Actor, Context, Handler, Message, MessageResult};
 | 
					use actix::{Actor, Context, Handler, Message, MessageResult};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::data::entity_manager::EntityManager;
 | 
					use crate::data::entity_manager::EntityManager;
 | 
				
			||||||
use crate::data::user::{User, verify_password};
 | 
					use crate::data::user::{User, UserID, verify_password};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub enum LoginResult {
 | 
					pub enum LoginResult {
 | 
				
			||||||
@@ -17,6 +17,18 @@ pub struct LoginRequest {
 | 
				
			|||||||
    pub password: String,
 | 
					    pub password: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct ChangePasswordResult(pub bool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Message)]
 | 
				
			||||||
 | 
					#[rtype(ChangePasswordResult)]
 | 
				
			||||||
 | 
					pub struct ChangePasswordRequest {
 | 
				
			||||||
 | 
					    pub user_id: UserID,
 | 
				
			||||||
 | 
					    pub new_password: String,
 | 
				
			||||||
 | 
					    pub temporary: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct UsersActor {
 | 
					pub struct UsersActor {
 | 
				
			||||||
    manager: EntityManager<User>,
 | 
					    manager: EntityManager<User>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -46,4 +58,13 @@ impl Handler<LoginRequest> for UsersActor {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Handler<ChangePasswordRequest> for UsersActor {
 | 
				
			||||||
 | 
					    type Result = MessageResult<ChangePasswordRequest>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle(&mut self, msg: ChangePasswordRequest, _ctx: &mut Self::Context) -> Self::Result {
 | 
				
			||||||
 | 
					        MessageResult(ChangePasswordResult(
 | 
				
			||||||
 | 
					            self.manager.change_user_password(&msg.user_id, &msg.new_password, msg.temporary)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -12,4 +12,7 @@ pub const APP_NAME: &str = "Basic OIDC";
 | 
				
			|||||||
pub const MAX_SESSION_DURATION: u64 = 60 * 30;
 | 
					pub const MAX_SESSION_DURATION: u64 = 60 * 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Minimum interval between each last activity record in session
 | 
					/// Minimum interval between each last activity record in session
 | 
				
			||||||
pub const MIN_ACTIVITY_RECORD_TIME: u64 = 10;
 | 
					pub const MIN_ACTIVITY_RECORD_TIME: u64 = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Minimum password length
 | 
				
			||||||
 | 
					pub const MIN_PASS_LEN: usize = 4;
 | 
				
			||||||
@@ -3,11 +3,11 @@ use actix_identity::Identity;
 | 
				
			|||||||
use actix_web::{HttpResponse, Responder, web};
 | 
					use actix_web::{HttpResponse, Responder, web};
 | 
				
			||||||
use askama::Template;
 | 
					use askama::Template;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::actors::users_actor::{LoginResult, UsersActor};
 | 
					use crate::actors::users_actor::{ChangePasswordResult, LoginResult, UsersActor};
 | 
				
			||||||
use crate::actors::users_actor;
 | 
					use crate::actors::users_actor;
 | 
				
			||||||
use crate::constants::APP_NAME;
 | 
					use crate::constants::{APP_NAME, MIN_PASS_LEN};
 | 
				
			||||||
use crate::controllers::base_controller::redirect_user;
 | 
					use crate::controllers::base_controller::redirect_user;
 | 
				
			||||||
use crate::data::session_identity::SessionIdentity;
 | 
					use crate::data::session_identity::{SessionIdentity, SessionStatus};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Template)]
 | 
					#[derive(Template)]
 | 
				
			||||||
#[template(path = "base_login_page.html")]
 | 
					#[template(path = "base_login_page.html")]
 | 
				
			||||||
@@ -25,6 +25,13 @@ struct LoginTemplate {
 | 
				
			|||||||
    login: String,
 | 
					    login: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Template)]
 | 
				
			||||||
 | 
					#[template(path = "password_reset.html")]
 | 
				
			||||||
 | 
					struct PasswordResetTemplate {
 | 
				
			||||||
 | 
					    _parent: BaseLoginPage,
 | 
				
			||||||
 | 
					    min_pass_len: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Deserialize)]
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
pub struct LoginRequestBody {
 | 
					pub struct LoginRequestBody {
 | 
				
			||||||
    login: String,
 | 
					    login: String,
 | 
				
			||||||
@@ -56,9 +63,29 @@ pub async fn login_route(users: web::Data<Addr<UsersActor>>,
 | 
				
			|||||||
        return redirect_user("/");
 | 
					        return redirect_user("/");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check if user is setting a new  password
 | 
				
			||||||
 | 
					    if let (Some(req), true) = (&req, SessionIdentity(&id).need_new_password()) {
 | 
				
			||||||
 | 
					        if req.password.len() < MIN_PASS_LEN {
 | 
				
			||||||
 | 
					            danger = "Password is too short!".to_string();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let res: ChangePasswordResult = users.send(users_actor::ChangePasswordRequest {
 | 
				
			||||||
 | 
					                user_id: SessionIdentity(&id).user_id(),
 | 
				
			||||||
 | 
					                new_password: req.password.clone(),
 | 
				
			||||||
 | 
					                temporary: false,
 | 
				
			||||||
 | 
					            }).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if !res.0 {
 | 
				
			||||||
 | 
					                danger = "Failed to change password!".to_string();
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                SessionIdentity(&id).set_status(SessionStatus::SignedIn);
 | 
				
			||||||
 | 
					                return redirect_user("/");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Try to authenticate user
 | 
					    // Try to authenticate user
 | 
				
			||||||
    if let Some(req) = &req {
 | 
					    else if let Some(req) = &req {
 | 
				
			||||||
        // TODO : check request origin
 | 
					        // TODO : check request origin (check for valid Referer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        login = req.login.clone();
 | 
					        login = req.login.clone();
 | 
				
			||||||
        let response: LoginResult = users.send(users_actor::LoginRequest {
 | 
					        let response: LoginResult = users.send(users_actor::LoginRequest {
 | 
				
			||||||
@@ -70,7 +97,11 @@ pub async fn login_route(users: web::Data<Addr<UsersActor>>,
 | 
				
			|||||||
            LoginResult::Success(user) => {
 | 
					            LoginResult::Success(user) => {
 | 
				
			||||||
                SessionIdentity(&id).set_user(&user);
 | 
					                SessionIdentity(&id).set_user(&user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return redirect_user("/");
 | 
					                if user.need_reset_password {
 | 
				
			||||||
 | 
					                    SessionIdentity(&id).set_status(SessionStatus::NeedNewPassword);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return redirect_user("/");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            c => {
 | 
					            c => {
 | 
				
			||||||
@@ -81,6 +112,21 @@ pub async fn login_route(users: web::Data<Addr<UsersActor>>,
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Display password reset form if it is appropriate
 | 
				
			||||||
 | 
					    if SessionIdentity(&id).need_new_password() {
 | 
				
			||||||
 | 
					        return HttpResponse::Ok()
 | 
				
			||||||
 | 
					            .content_type("text/html")
 | 
				
			||||||
 | 
					            .body(PasswordResetTemplate {
 | 
				
			||||||
 | 
					                _parent: BaseLoginPage {
 | 
				
			||||||
 | 
					                    page_title: "Password reset",
 | 
				
			||||||
 | 
					                    danger,
 | 
				
			||||||
 | 
					                    success,
 | 
				
			||||||
 | 
					                    app_name: APP_NAME,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                min_pass_len: MIN_PASS_LEN,
 | 
				
			||||||
 | 
					            }.render().unwrap());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HttpResponse::Ok()
 | 
					    HttpResponse::Ok()
 | 
				
			||||||
        .content_type("text/html")
 | 
					        .content_type("text/html")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,17 @@ impl<E> EntityManager<E> where E: serde::Serialize + serde::de::DeserializeOwned
 | 
				
			|||||||
        self.save()
 | 
					        self.save()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Replace entries in the list that matches a criteria
 | 
				
			||||||
 | 
					    pub fn replace_entries<F>(&mut self, filter: F, el: &E) -> Res where F: Fn(&E) -> bool {
 | 
				
			||||||
 | 
					        for i in 0..self.list.len() {
 | 
				
			||||||
 | 
					            if filter(&self.list[i]) {
 | 
				
			||||||
 | 
					                self.list[i] = el.clone();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.save()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Iterate over the entries of this entity manager
 | 
					    /// Iterate over the entries of this entity manager
 | 
				
			||||||
    pub fn iter(&self) -> Iter<'_, E> {
 | 
					    pub fn iter(&self) -> Iter<'_, E> {
 | 
				
			||||||
        self.list.iter()
 | 
					        self.list.iter()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,18 +6,28 @@ use crate::data::user::User;
 | 
				
			|||||||
use crate::utils::time::time;
 | 
					use crate::utils::time::time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
 | 
					#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
 | 
				
			||||||
enum SessionStatus {
 | 
					pub enum SessionStatus {
 | 
				
			||||||
 | 
					    Invalid,
 | 
				
			||||||
    SignedIn,
 | 
					    SignedIn,
 | 
				
			||||||
    NeedNewPassword,
 | 
					    NeedNewPassword,
 | 
				
			||||||
    NeedMFA,
 | 
					    NeedMFA,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize)]
 | 
					impl Default for SessionStatus {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self::Invalid
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Serialize, Deserialize, Default)]
 | 
				
			||||||
struct SessionIdentityData {
 | 
					struct SessionIdentityData {
 | 
				
			||||||
    pub id: String,
 | 
					    pub id: String,
 | 
				
			||||||
    pub is_admin: bool,
 | 
					    pub is_admin: bool,
 | 
				
			||||||
    last_access: u64,
 | 
					    last_access: u64,
 | 
				
			||||||
    pub status: SessionStatus,
 | 
					    pub status: SessionStatus,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO : add session max duration (1 day)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct SessionIdentity<'a>(pub &'a Identity);
 | 
					pub struct SessionIdentity<'a>(pub &'a Identity);
 | 
				
			||||||
@@ -39,6 +49,13 @@ impl<'a> SessionIdentity<'a> {
 | 
				
			|||||||
            .map(serde_json::from_str)
 | 
					            .map(serde_json::from_str)
 | 
				
			||||||
            .map(|f| f.expect("Failed to deserialize session data!"));
 | 
					            .map(|f| f.expect("Failed to deserialize session data!"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if session is valid
 | 
				
			||||||
 | 
					        if let Some(sess) = &res {
 | 
				
			||||||
 | 
					            if sess.id.is_empty() {
 | 
				
			||||||
 | 
					                return None;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(session) = res.as_mut() {
 | 
					        if let Some(session) = res.as_mut() {
 | 
				
			||||||
            if session.last_access + MAX_SESSION_DURATION < time() {
 | 
					            if session.last_access + MAX_SESSION_DURATION < time() {
 | 
				
			||||||
                log::info!("Session is expired for {}", session.id);
 | 
					                log::info!("Session is expired for {}", session.id);
 | 
				
			||||||
@@ -63,9 +80,27 @@ impl<'a> SessionIdentity<'a> {
 | 
				
			|||||||
        self.0.remember(s);
 | 
					        self.0.remember(s);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn set_status(&self, status: SessionStatus) {
 | 
				
			||||||
 | 
					        let mut sess = self.get_session_data().unwrap_or_default();
 | 
				
			||||||
 | 
					        sess.status = status;
 | 
				
			||||||
 | 
					        self.set_session_data(&sess);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn is_authenticated(&self) -> bool {
 | 
					    pub fn is_authenticated(&self) -> bool {
 | 
				
			||||||
        self.get_session_data()
 | 
					        self.get_session_data()
 | 
				
			||||||
            .map(|s| s.status == SessionStatus::SignedIn)
 | 
					            .map(|s| s.status == SessionStatus::SignedIn)
 | 
				
			||||||
            .unwrap_or(false)
 | 
					            .unwrap_or(false)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn need_new_password(&self) -> bool {
 | 
				
			||||||
 | 
					        self.get_session_data()
 | 
				
			||||||
 | 
					            .map(|s| s.status == SessionStatus::NeedNewPassword)
 | 
				
			||||||
 | 
					            .unwrap_or(false)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn user_id(&self) -> String {
 | 
				
			||||||
 | 
					        self.get_session_data()
 | 
				
			||||||
 | 
					            .unwrap_or_default()
 | 
				
			||||||
 | 
					            .id
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,9 +2,11 @@ use crate::data::entity_manager::EntityManager;
 | 
				
			|||||||
use crate::data::service::ServiceID;
 | 
					use crate::data::service::ServiceID;
 | 
				
			||||||
use crate::utils::err::Res;
 | 
					use crate::utils::err::Res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type UserID = String;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    pub uid: String,
 | 
					    pub uid: UserID,
 | 
				
			||||||
    pub first_name: String,
 | 
					    pub first_name: String,
 | 
				
			||||||
    pub last_last: String,
 | 
					    pub last_last: String,
 | 
				
			||||||
    pub username: String,
 | 
					    pub username: String,
 | 
				
			||||||
@@ -67,4 +69,44 @@ impl EntityManager<User> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn find_by_user_id(&self, id: &UserID) -> Option<User> {
 | 
				
			||||||
 | 
					        for entry in self.iter() {
 | 
				
			||||||
 | 
					            if entry.uid.eq(id) {
 | 
				
			||||||
 | 
					                return Some(entry.clone());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Update user information
 | 
				
			||||||
 | 
					    fn update_user<F>(&mut self, id: &UserID, update: F) -> bool where F: FnOnce(User) -> User {
 | 
				
			||||||
 | 
					        let user = match self.find_by_user_id(id) {
 | 
				
			||||||
 | 
					            None => return false,
 | 
				
			||||||
 | 
					            Some(user) => user
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Err(e) = self.replace_entries(|u| u.uid.eq(id), &update(user)) {
 | 
				
			||||||
 | 
					            log::error!("Failed to update user information! {:?}", e);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn change_user_password(&mut self, id: &UserID, password: &str, temporary: bool) -> bool {
 | 
				
			||||||
 | 
					        let new_hash = match hash_password(password) {
 | 
				
			||||||
 | 
					            Ok(h) => { h }
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                log::error!("Failed to hash user password! {}", e);
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.update_user(id, |mut user| {
 | 
				
			||||||
 | 
					            user.password = new_hash;
 | 
				
			||||||
 | 
					            user.need_reset_password = temporary;
 | 
				
			||||||
 | 
					            user
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										53
									
								
								templates/password_reset.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								templates/password_reset.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					{% extends "base_login_page.html" %}
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<form action="/login" method="post" id="reset_password_form">
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					        <p>You need to configure a new password:</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <p style="color:red" id="err_target"></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Needed for controller -->
 | 
				
			||||||
 | 
					        <input type="hidden" name="login" value="."/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="form-floating">
 | 
				
			||||||
 | 
					            <input name="password" type="password" required class="form-control" id="pass1"
 | 
				
			||||||
 | 
					                   placeholder="unsername"/>
 | 
				
			||||||
 | 
					            <label for="pass1">New password</label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="form-floating">
 | 
				
			||||||
 | 
					            <input type="password" required class="form-control" id="pass2"
 | 
				
			||||||
 | 
					                   placeholder="Password"/>
 | 
				
			||||||
 | 
					            <label for="pass2">Confirm new password</label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <button class="w-100 btn btn-lg btn-primary" type="submit">Change password</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div style="margin-top: 10px;">
 | 
				
			||||||
 | 
					        <a href="/logout">Sign out</a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    const form = document.getElementById("reset_password_form");
 | 
				
			||||||
 | 
					    const error_target = document.getElementById("err_target");
 | 
				
			||||||
 | 
					    form.addEventListener("submit", (e) => {
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        error_target.innerHTML = "";
 | 
				
			||||||
 | 
					        const pass1 = document.getElementById("pass1");
 | 
				
			||||||
 | 
					        const pass2 = document.getElementById("pass2");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (pass1.value.length < {{ min_pass_len }})
 | 
				
			||||||
 | 
					            error_target.innerHTML = "Your password must have at least {{ min_pass_len }} characters!";
 | 
				
			||||||
 | 
					        else if (pass1.value !== pass2.value)
 | 
				
			||||||
 | 
					            error_target.innerHTML = "The password and its confirmation are not the same !";
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            form.submit();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock content %}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user