Browse Source

[Web] Force user pass update via Modal

FreddleSpl0it 1 year ago
parent
commit
2f401c9fc4

+ 6 - 0
data/web/inc/ajax/destroy_pw_update.php

@@ -0,0 +1,6 @@
+<?php
+session_start();
+unset($_SESSION['pending_mailcow_cc_username']);
+unset($_SESSION['pending_mailcow_cc_role']);
+unset($_SESSION['pending_pw_update']);
+?>

+ 3 - 2
data/web/inc/footer.inc.php

@@ -32,11 +32,11 @@ foreach($_SESSION['pending_tfa_methods'] as $authdata){
 if (isset($pending_tfa_authmechs['webauthn'])) {
 if (isset($pending_tfa_authmechs['webauthn'])) {
   $pending_tfa_authmechs['webauthn'] = true;
   $pending_tfa_authmechs['webauthn'] = true;
 }
 }
-if (!isset($pending_tfa_authmechs['webauthn']) 
+if (!isset($pending_tfa_authmechs['webauthn'])
     && isset($pending_tfa_authmechs['yubi_otp'])) {
     && isset($pending_tfa_authmechs['yubi_otp'])) {
   $pending_tfa_authmechs['yubi_otp'] = true;
   $pending_tfa_authmechs['yubi_otp'] = true;
 }
 }
-if (!isset($pending_tfa_authmechs['webauthn']) 
+if (!isset($pending_tfa_authmechs['webauthn'])
     && !isset($pending_tfa_authmechs['yubi_otp'])
     && !isset($pending_tfa_authmechs['yubi_otp'])
     && isset($pending_tfa_authmechs['totp'])) {
     && isset($pending_tfa_authmechs['totp'])) {
   $pending_tfa_authmechs['totp'] = true;
   $pending_tfa_authmechs['totp'] = true;
@@ -60,6 +60,7 @@ $globalVariables = [
   ),
   ),
   'js_path' => '/cache/'.basename($JSPath),
   'js_path' => '/cache/'.basename($JSPath),
   'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'],
   'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'],
+  'pending_pw_update' => @$_SESSION['pending_pw_update'],
   'pending_tfa_authmechs' => $pending_tfa_authmechs,
   'pending_tfa_authmechs' => $pending_tfa_authmechs,
   'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'],
   'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'],
   'lang_footer' => json_encode($lang['footer']),
   'lang_footer' => json_encode($lang['footer']),

+ 52 - 42
data/web/inc/functions.inc.php

@@ -905,7 +905,7 @@ function check_login($user, $pass, $app_passwd_data = false) {
   }
   }
 
 
   // Validate mailbox user
   // Validate mailbox user
-  $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+  $stmt = $pdo->prepare("SELECT `password`, JSON_UNQUOTE(JSON_EXTRACT(`attributes`, '$.force_pw_update')) AS `force_pw_update` FROM `mailbox`
       INNER JOIN domain on mailbox.domain = domain.domain
       INNER JOIN domain on mailbox.domain = domain.domain
       WHERE `kind` NOT REGEXP 'location|thing|group'
       WHERE `kind` NOT REGEXP 'location|thing|group'
         AND `mailbox`.`active`='1'
         AND `mailbox`.`active`='1'
@@ -939,11 +939,17 @@ function check_login($user, $pass, $app_passwd_data = false) {
     $stmt->execute(array(':user' => $user));
     $stmt->execute(array(':user' => $user));
     $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
     $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
   }
   }
-  foreach ($rows as $row) { 
+  foreach ($rows as $row) {
     // verify password
     // verify password
     if (verify_hash($row['password'], $pass) !== false) {
     if (verify_hash($row['password'], $pass) !== false) {
-      if (!array_key_exists("app_passwd_id", $row)){ 
+      if (!array_key_exists("app_passwd_id", $row)){
         // password is not a app password
         // password is not a app password
+
+        // check if pw update is required
+        if (array_key_exists('force_pw_update', $row) && intval($row['force_pw_update']) == 1) {
+          $_SESSION['pending_pw_update'] = True;
+        }
+
         // check for tfa authenticators
         // check for tfa authenticators
         $authenticators = get_tfa($user);
         $authenticators = get_tfa($user);
         if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&
         if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&
@@ -1028,7 +1034,7 @@ function update_sogo_static_view($mailbox = null) {
     // Check if the mailbox exists
     // Check if the mailbox exists
     $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
     $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
     $stmt->execute(array(':mailbox' => $mailbox));
     $stmt->execute(array(':mailbox' => $mailbox));
-    $row = $stmt->fetch(PDO::FETCH_ASSOC);  
+    $row = $stmt->fetch(PDO::FETCH_ASSOC);
     if ($row){
     if ($row){
       $mailbox_exists = true;
       $mailbox_exists = true;
     }
     }
@@ -1056,7 +1062,7 @@ function update_sogo_static_view($mailbox = null) {
               LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
               LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
             WHERE
             WHERE
               mailbox.active = '1'";
               mailbox.active = '1'";
-  
+
   if ($mailbox_exists) {
   if ($mailbox_exists) {
     $query .= " AND mailbox.username = :mailbox";
     $query .= " AND mailbox.username = :mailbox";
     $stmt = $pdo->prepare($query);
     $stmt = $pdo->prepare($query);
@@ -1065,9 +1071,9 @@ function update_sogo_static_view($mailbox = null) {
     $query .= " GROUP BY mailbox.username";
     $query .= " GROUP BY mailbox.username";
     $stmt = $pdo->query($query);
     $stmt = $pdo->query($query);
   }
   }
-  
+
   $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
   $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
-  
+
   flush_memcached();
   flush_memcached();
 }
 }
 function edit_user_account($_data) {
 function edit_user_account($_data) {
@@ -1079,9 +1085,10 @@ function edit_user_account($_data) {
   !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
   !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
   !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
   !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
 
 
-  $username = $_SESSION['mailcow_cc_username'];
-  $role = $_SESSION['mailcow_cc_role'];
+  $username = (!empty($_data['username'])) ? $_data['username'] : $_SESSION['mailcow_cc_username'];
+  $role = (!empty($_data['role'])) ? $_data['role'] : $_SESSION['mailcow_cc_role'];
   $password_old = $_data['user_old_pass'];
   $password_old = $_data['user_old_pass'];
+  $skip_old_password_check = $_data['skip_old_password_check'];
   $pw_recovery_email = $_data['pw_recovery_email'];
   $pw_recovery_email = $_data['pw_recovery_email'];
 
 
   if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
   if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
@@ -1094,22 +1101,24 @@ function edit_user_account($_data) {
   }
   }
 
 
   // edit password
   // edit password
-  if (!empty($password_old) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
-    $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
-        WHERE `kind` NOT REGEXP 'location|thing|group'
-          AND `username` = :user");
-    $stmt->execute(array(':user' => $username));
-    $row = $stmt->fetch(PDO::FETCH_ASSOC);
-  
-    if (!verify_hash($row['password'], $password_old)) {
-      $_SESSION['return'][] =  array(
-        'type' => 'danger',
-        'log' => array(__FUNCTION__, $_data_log),
-        'msg' => 'access_denied'
-      );
-      return false;
+  if ((!empty($password_old) || $skip_old_password_check) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
+    if (!$skip_old_password_check) {
+      $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
+          WHERE `kind` NOT REGEXP 'location|thing|group'
+            AND `username` = :user");
+      $stmt->execute(array(':user' => $username));
+      $row = $stmt->fetch(PDO::FETCH_ASSOC);
+
+      if (!verify_hash($row['password'], $password_old)) {
+        $_SESSION['return'][] =  array(
+          'type' => 'danger',
+          'log' => array(__FUNCTION__, $_data_log),
+          'msg' => 'access_denied'
+        );
+        return false;
+      }
     }
     }
-  
+
     $password_new = $_data['user_new_pass'];
     $password_new = $_data['user_new_pass'];
     $password_new2  = $_data['user_new_pass2'];
     $password_new2  = $_data['user_new_pass2'];
     if (password_check($password_new, $password_new2) !== true) {
     if (password_check($password_new, $password_new2) !== true) {
@@ -1124,7 +1133,7 @@ function edit_user_account($_data) {
       ':password_hashed' => $password_hashed,
       ':password_hashed' => $password_hashed,
       ':username' => $username
       ':username' => $username
     ));
     ));
-  
+
     update_sogo_static_view();
     update_sogo_static_view();
   }
   }
   // edit password recovery email
   // edit password recovery email
@@ -1152,6 +1161,7 @@ function edit_user_account($_data) {
     'log' => array(__FUNCTION__, $_data_log),
     'log' => array(__FUNCTION__, $_data_log),
     'msg' => array('mailbox_modified', htmlspecialchars($username))
     'msg' => array('mailbox_modified', htmlspecialchars($username))
   );
   );
+  return true;
 }
 }
 function user_get_alias_details($username) {
 function user_get_alias_details($username) {
   global $pdo;
   global $pdo;
@@ -1374,7 +1384,7 @@ function set_tfa($_data) {
             $_data['registration']->certificate,
             $_data['registration']->certificate,
             0
             0
         ));
         ));
-    
+
         $_SESSION['return'][] =  array(
         $_SESSION['return'][] =  array(
             'type' => 'success',
             'type' => 'success',
             'log' => array(__FUNCTION__, $_data_log),
             'log' => array(__FUNCTION__, $_data_log),
@@ -1544,7 +1554,7 @@ function unset_tfa_key($_data) {
 
 
   try {
   try {
     if (!is_numeric($id)) $access_denied = true;
     if (!is_numeric($id)) $access_denied = true;
-    
+
     // set access_denied error
     // set access_denied error
     if ($access_denied){
     if ($access_denied){
       $_SESSION['return'][] = array(
       $_SESSION['return'][] = array(
@@ -1553,7 +1563,7 @@ function unset_tfa_key($_data) {
         'msg' => 'access_denied'
         'msg' => 'access_denied'
       );
       );
       return false;
       return false;
-    } 
+    }
 
 
     // check if it's last key
     // check if it's last key
     $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
     $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
@@ -1602,7 +1612,7 @@ function get_tfa($username = null, $id = null) {
         WHERE `username` = :username AND `active` = '1'");
         WHERE `username` = :username AND `active` = '1'");
     $stmt->execute(array(':username' => $username));
     $stmt->execute(array(':username' => $username));
     $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
     $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
- 
+
     // no tfa methods found
     // no tfa methods found
     if (count($results) == 0) {
     if (count($results) == 0) {
         $data['name'] = 'none';
         $data['name'] = 'none';
@@ -1810,8 +1820,8 @@ function verify_tfa_login($username, $_data) {
                   'msg' => array('webauthn_authenticator_failed')
                   'msg' => array('webauthn_authenticator_failed')
               );
               );
               return false;
               return false;
-            } 
-            
+            }
+
             if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) {
             if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) {
                 $_SESSION['return'][] =  array(
                 $_SESSION['return'][] =  array(
                     'type' => 'danger',
                     'type' => 'danger',
@@ -2173,7 +2183,7 @@ function cors($action, $data = null) {
           'msg' => 'access_denied'
           'msg' => 'access_denied'
         );
         );
         return false;
         return false;
-      }    
+      }
 
 
       $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
       $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
       $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
       $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
@@ -2206,7 +2216,7 @@ function cors($action, $data = null) {
         $redis->hMSet('CORS_SETTINGS', array(
         $redis->hMSet('CORS_SETTINGS', array(
           'allowed_origins' => implode(', ', $allowed_origins),
           'allowed_origins' => implode(', ', $allowed_origins),
           'allowed_methods' => implode(', ', $allowed_methods)
           'allowed_methods' => implode(', ', $allowed_methods)
-        ));   
+        ));
       } catch (RedisException $e) {
       } catch (RedisException $e) {
         $_SESSION['return'][] = array(
         $_SESSION['return'][] = array(
           'type' => 'danger',
           'type' => 'danger',
@@ -2258,10 +2268,10 @@ function cors($action, $data = null) {
       header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
       header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
 
 
       // Access-Control settings requested, this is just a preflight request
       // Access-Control settings requested, this is just a preflight request
-      if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && 
+      if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' &&
         isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) &&
         isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) &&
         isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
         isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
-  
+
         $allowed_methods = explode(', ', $cors_settings["allowed_methods"]);
         $allowed_methods = explode(', ', $cors_settings["allowed_methods"]);
         if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true))
         if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true))
           // method allowed send 200 OK
           // method allowed send 200 OK
@@ -2315,7 +2325,7 @@ function reset_password($action, $data = null) {
     break;
     break;
     case 'issue':
     case 'issue':
       $username = $data;
       $username = $data;
-      
+
       // perform cleanup
       // perform cleanup
       $stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);");
       $stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);");
       $stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME));
       $stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME));
@@ -2397,8 +2407,8 @@ function reset_password($action, $data = null) {
       $request_date = new DateTime();
       $request_date = new DateTime();
       $locale_date = locale_get_default();
       $locale_date = locale_get_default();
       $date_formatter = new IntlDateFormatter(
       $date_formatter = new IntlDateFormatter(
-        $locale_date, 
-        IntlDateFormatter::FULL, 
+        $locale_date,
+        IntlDateFormatter::FULL,
         IntlDateFormatter::FULL
         IntlDateFormatter::FULL
       );
       );
       $formatted_request_date = $date_formatter->format($request_date);
       $formatted_request_date = $date_formatter->format($request_date);
@@ -2514,7 +2524,7 @@ function reset_password($action, $data = null) {
       $stmt->execute(array(
       $stmt->execute(array(
         ':username' => $username
         ':username' => $username
       ));
       ));
-   
+
       $_SESSION['return'][] = array(
       $_SESSION['return'][] = array(
         'type' => 'success',
         'type' => 'success',
         'log' => array(__FUNCTION__, $action, $_data_log),
         'log' => array(__FUNCTION__, $action, $_data_log),
@@ -2557,7 +2567,7 @@ function reset_password($action, $data = null) {
       $text = $data['text'];
       $text = $data['text'];
       $html = $data['html'];
       $html = $data['html'];
       $subject = $data['subject'];
       $subject = $data['subject'];
-    
+
       if (!filter_var($from, FILTER_VALIDATE_EMAIL)) {
       if (!filter_var($from, FILTER_VALIDATE_EMAIL)) {
         $_SESSION['return'][] =  array(
         $_SESSION['return'][] =  array(
           'type' => 'danger',
           'type' => 'danger',
@@ -2590,7 +2600,7 @@ function reset_password($action, $data = null) {
         );
         );
         return false;
         return false;
       }
       }
-    
+
       ini_set('max_execution_time', 0);
       ini_set('max_execution_time', 0);
       ini_set('max_input_time', 0);
       ini_set('max_input_time', 0);
       $mail = new PHPMailer;
       $mail = new PHPMailer;
@@ -2622,7 +2632,7 @@ function reset_password($action, $data = null) {
         return false;
         return false;
       }
       }
       $mail->ClearAllRecipients();
       $mail->ClearAllRecipients();
-    
+
       return true;
       return true;
     break;
     break;
   }
   }

+ 51 - 21
data/web/inc/triggers.inc.php

@@ -10,15 +10,37 @@ if (!empty($_GET['sso_token'])) {
   }
   }
 }
 }
 
 
+
+if (isset($_POST["forced_pw_update"]) && !empty($_POST['new_password']) && !empty($_POST['new_password2'])) {
+  $result = edit_user_account(array(
+    'username' => $_SESSION['pending_mailcow_cc_username'],
+    'role' => $_SESSION['pending_mailcow_cc_role'],
+    'user_new_pass' => $_POST['new_password'],
+    'user_new_pass2' => $_POST['new_password2'],
+    'skip_old_password_check' => True
+  ));
+
+  if ($result) {
+    $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
+    $_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
+    unset($_SESSION['pending_mailcow_cc_username']);
+    unset($_SESSION['pending_mailcow_cc_role']);
+    unset($_SESSION['pending_pw_update']);
+  }
+
+  header("Location: /");
+  exit;
+}
+
 if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) {
 if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) {
-  reset_password("issue", $_POST['username']);
+  $resultreset_password("issue", $_POST['username']);
   header("Location: /");
   header("Location: /");
   exit;
   exit;
 }
 }
 if (isset($_POST["pw_reset"])) {
 if (isset($_POST["pw_reset"])) {
   $username = reset_password("check", $_POST['token']);
   $username = reset_password("check", $_POST['token']);
   $reset_result = reset_password("reset", array(
   $reset_result = reset_password("reset", array(
-    'new_password' => $_POST['new_password'], 
+    'new_password' => $_POST['new_password'],
     'new_password2' => $_POST['new_password2'],
     'new_password2' => $_POST['new_password2'],
     'token' => $_POST['token'],
     'token' => $_POST['token'],
     'username' => $username,
     'username' => $username,
@@ -47,13 +69,15 @@ if (isset($_POST["verify_tfa_login"])) {
       header("Location: /");
       header("Location: /");
       exit;
       exit;
     } else {
     } else {
-      $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
-      $_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
-      unset($_SESSION['pending_mailcow_cc_username']);
-      unset($_SESSION['pending_mailcow_cc_role']);
-      unset($_SESSION['pending_tfa_methods']);
-  
-      header("Location: /user");
+      if (!$_SESSION['pending_pw_update']) {
+        $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
+        $_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
+        unset($_SESSION['pending_mailcow_cc_username']);
+        unset($_SESSION['pending_mailcow_cc_role']);
+        unset($_SESSION['pending_tfa_methods']);
+
+        header("Location: /user");
+      }
     }
     }
   } else {
   } else {
     unset($_SESSION['pending_pw_reset_token']);
     unset($_SESSION['pending_pw_reset_token']);
@@ -97,24 +121,30 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
 		header("Location: /mailbox");
 		header("Location: /mailbox");
 	}
 	}
 	elseif ($as == "user") {
 	elseif ($as == "user") {
-		$_SESSION['mailcow_cc_username'] = $login_user;
-		$_SESSION['mailcow_cc_role'] = "user";
-        $http_parameters = explode('&', $_SESSION['index_query_string']);
-        unset($_SESSION['index_query_string']);
-        if (in_array('mobileconfig', $http_parameters)) {
-            if (in_array('only_email', $http_parameters)) {
-                header("Location: /mobileconfig.php?only_email");
-                die();
-            }
-            header("Location: /mobileconfig.php");
-            die();
+    if ($_SESSION['pending_pw_update']) {
+      $_SESSION['pending_mailcow_cc_username'] = $login_user;
+      $_SESSION['pending_mailcow_cc_role'] = "user";
+    } else {
+      $_SESSION['mailcow_cc_username'] = $login_user;
+      $_SESSION['mailcow_cc_role'] = "user";
+      $http_parameters = explode('&', $_SESSION['index_query_string']);
+      unset($_SESSION['index_query_string']);
+      if (in_array('mobileconfig', $http_parameters)) {
+        if (in_array('only_email', $http_parameters)) {
+          header("Location: /mobileconfig.php?only_email");
+          die();
         }
         }
-		header("Location: /user");
+        header("Location: /mobileconfig.php");
+        die();
+      }
+      header("Location: /user");
+    }
 	}
 	}
 	elseif ($as != "pending") {
 	elseif ($as != "pending") {
     unset($_SESSION['pending_mailcow_cc_username']);
     unset($_SESSION['pending_mailcow_cc_username']);
     unset($_SESSION['pending_mailcow_cc_role']);
     unset($_SESSION['pending_mailcow_cc_role']);
     unset($_SESSION['pending_tfa_methods']);
     unset($_SESSION['pending_tfa_methods']);
+    unset($_SESSION['pending_pw_update']);
 		unset($_SESSION['mailcow_cc_username']);
 		unset($_SESSION['mailcow_cc_username']);
 		unset($_SESSION['mailcow_cc_role']);
 		unset($_SESSION['mailcow_cc_role']);
 	}
 	}

+ 33 - 10
data/web/templates/base.twig

@@ -211,8 +211,31 @@ function recursiveBase64StrToArrayBuffer(obj) {
     mailcow_alert_box('{{ alert_msg|raw|e("js") }}', '{{ alert_type }}');
     mailcow_alert_box('{{ alert_msg|raw|e("js") }}', '{{ alert_type }}');
     {% endfor %}
     {% endfor %}
 
 
+
+    // Confirm PW Update modal
+    {% if pending_pw_update %}
+    new bootstrap.Modal(document.getElementById("ConfirmPWUpdateModal"), {
+      backdrop: 'static',
+      keyboard: false
+    }).show();
+
+    $('#ConfirmPWUpdateModal').on('hidden.bs.modal', function(){
+      // cancel pending login
+      $.ajax({
+        type: "GET",
+        cache: false,
+        dataType: 'script',
+        url: '/inc/ajax/destroy_pw_update.php',
+        complete: function(data){
+          window.location = window.location.href.split("#")[0];
+        }
+      });
+    });
+    {% endif %}
+
+
     // Confirm TFA modal
     // Confirm TFA modal
-  {% if pending_tfa_methods %}
+    {% if pending_tfa_methods %}
     new bootstrap.Modal(document.getElementById("ConfirmTFAModal"), {
     new bootstrap.Modal(document.getElementById("ConfirmTFAModal"), {
       backdrop: 'static',
       backdrop: 'static',
       keyboard: false
       keyboard: false
@@ -235,7 +258,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
     $(".totp-authenticator-selection").click(function(){
     $(".totp-authenticator-selection").click(function(){
       $(".totp-authenticator-selection").removeClass("active");
       $(".totp-authenticator-selection").removeClass("active");
       $(this).addClass("active");
       $(this).addClass("active");
-      
+
       var id = $(this).children('input').first().val();
       var id = $(this).children('input').first().val();
       $("#totp_selected_id").val(id);
       $("#totp_selected_id").val(id);
 
 
@@ -244,7 +267,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
     if ($('.totp-authenticator-selection').length == 1 &&
     if ($('.totp-authenticator-selection').length == 1 &&
         $('#pending_tfa_tab_yubi_otp').length == 0 &&
         $('#pending_tfa_tab_yubi_otp').length == 0 &&
         $('.webauthn-authenticator-selection').length == 0){
         $('.webauthn-authenticator-selection').length == 0){
-      
+
       // select default if only one authenticator exists
       // select default if only one authenticator exists
       $('.totp-authenticator-selection').addClass("active");
       $('.totp-authenticator-selection').addClass("active");
 
 
@@ -257,7 +280,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
     $('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
     $('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
       // autofocus
       // autofocus
       setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
       setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
-    });    
+    });
     // validate Yubi OTP tfa
     // validate Yubi OTP tfa
     if ($('.webauthn-authenticator-selection').length == 0){
     if ($('.webauthn-authenticator-selection').length == 0){
       // autofocus
       // autofocus
@@ -276,10 +299,10 @@ function recursiveBase64StrToArrayBuffer(obj) {
     $(".webauthn-authenticator-selection").click(function(){
     $(".webauthn-authenticator-selection").click(function(){
       $(".webauthn-authenticator-selection").removeClass("active");
       $(".webauthn-authenticator-selection").removeClass("active");
       $(this).addClass("active");
       $(this).addClass("active");
-      
+
       var id = $(this).children('input').first().val();
       var id = $(this).children('input').first().val();
       $("#webauthn_selected_id").val(id);
       $("#webauthn_selected_id").val(id);
-      
+
       var webauthn_status_auth = document.getElementById('webauthn_status_auth');
       var webauthn_status_auth = document.getElementById('webauthn_status_auth');
       webauthn_status_auth.style.setProperty('display', 'flex', 'important');
       webauthn_status_auth.style.setProperty('display', 'flex', 'important');
       var webauthn_return_code = document.getElementById('webauthn_return_code');
       var webauthn_return_code = document.getElementById('webauthn_return_code');
@@ -302,7 +325,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
           console.log(json);
           console.log(json);
           if (json.success === false) throw new Error();
           if (json.success === false) throw new Error();
           if (json.type === "error") throw new Error(json.msg);
           if (json.type === "error") throw new Error(json.msg);
-      
+
           recursiveBase64StrToArrayBuffer(json);
           recursiveBase64StrToArrayBuffer(json);
           return json;
           return json;
         }).then(getCredentialArgs => {
         }).then(getCredentialArgs => {
@@ -329,7 +352,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
           webauthn_return_code.style.setProperty('display', 'block', 'important');
           webauthn_return_code.style.setProperty('display', 'block', 'important');
           webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
           webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
         });
         });
-      } 
+      }
     });
     });
     $('#ConfirmTFAModal').on('hidden.bs.modal', function(){
     $('#ConfirmTFAModal').on('hidden.bs.modal', function(){
       // cancel pending login
       // cancel pending login
@@ -343,7 +366,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
         }
         }
       });
       });
     });
     });
-  {% endif %}
+    {% endif %}
 
 
 
 
     // Validate FIDO2
     // Validate FIDO2
@@ -540,7 +563,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
         Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }}
         Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }}
     </a>
     </a>
   </span>
   </span>
-  {% endif %}  
+  {% endif %}
   {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
   {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
   <span class="version">
   <span class="version">
     🛠️🐮 + 🐋 = 💕
     🛠️🐮 + 🐋 = 💕

+ 25 - 3
data/web/templates/modals/footer.twig

@@ -139,8 +139,7 @@
         <h3 class="modal-title">{{ lang.tfa.tfa }}</h3>
         <h3 class="modal-title">{{ lang.tfa.tfa }}</h3>
         <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
         <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
       </div>
       </div>
-      
-      
+
       <div class="modal-body p-0 pt-4">
       <div class="modal-body p-0 pt-4">
         <ul class="nav nav-tabs px-1" id="tabContent">
         <ul class="nav nav-tabs px-1" id="tabContent">
             {% if pending_tfa_authmechs["webauthn"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
             {% if pending_tfa_authmechs["webauthn"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
@@ -241,7 +240,7 @@
             <div role="tabpanel" class="tab-pane {% if pending_tfa_authmechs["totp"] %}active{% endif %}" id="tfa_tab_totp">
             <div role="tabpanel" class="tab-pane {% if pending_tfa_authmechs["totp"] %}active{% endif %}" id="tfa_tab_totp">
               <div class="card border-0" style="margin-bottom: 0px;">
               <div class="card border-0" style="margin-bottom: 0px;">
                   <div class="card-body">
                   <div class="card-body">
-                    <form role="form" method="post">        
+                    <form role="form" method="post">
                       <legend class="mt-2 mb-2">
                       <legend class="mt-2 mb-2">
                           <i class="bi bi-shield-fill-check"></i>
                           <i class="bi bi-shield-fill-check"></i>
                           {{ lang.tfa.authenticators }}
                           {{ lang.tfa.authenticators }}
@@ -311,6 +310,29 @@
   </div>
   </div>
 </div>
 </div>
 {% endif %}
 {% endif %}
+{% if pending_pw_update %}
+<div class="modal fade" id="ConfirmPWUpdateModal" tabindex="-1" role="dialog" aria-labelledby="ConfirmPWUpdateModalLabel">
+  <div class="modal-dialog" role="document">
+    <div class="modal-content">
+      <div class="modal-header">
+        <h3 class="modal-title">{{ lang.user.change_password }}</h3>
+        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
+      </div>
+
+      <div class="modal-body">
+        <form method="post" autofill="off">
+          <input type="password" autocorrect="off" autocapitalize="none" class="form-control mb-2" name="new_password" placeholder="{{ lang.login.new_password }}" />
+          <input type="password" autocorrect="off" autocapitalize="none" class="form-control mb-2" name="new_password2" placeholder="{{ lang.login.new_password_confirm }}" />
+
+          <div class="d-flex justify-content-end mt-4" style="position: relative">
+            <button type="submit" class="btn btn-xs-lg d-block d-sm-inline btn-success" name="forced_pw_update">{{ lang.user.change_password }}</button>
+          </div>
+        </form>
+      </div>
+    </div>
+  </div>
+</div>
+{% endif %}
 {% if mailcow_cc_role == 'admin' %}
 {% if mailcow_cc_role == 'admin' %}
 <div id="RestartContainer" class="modal fade" role="dialog">
 <div id="RestartContainer" class="modal fade" role="dialog">
   <div class="modal-dialog">
   <div class="modal-dialog">