Browse Source

[Web] Separate Login pages

FreddleSpl0it 7 months ago
parent
commit
aca01c8aa2

+ 13 - 4
data/web/debug.php → data/web/admin/dashboard.php

@@ -1,8 +1,17 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
 
-if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
-  header('Location: /');
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
+  header('Location: /admin');
   exit();
 }
 
@@ -16,7 +25,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
   $_SESSION['gal'] = json_decode($license_cache, true);
 }
 
-$js_minifier->add('/web/js/site/debug.js');
+$js_minifier->add('/web/js/site/dashboard.js');
 
 // vmail df
 $exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
@@ -61,7 +70,7 @@ foreach ($containers_info as $container => $container_info) {
 $hostname = getenv('MAILCOW_HOSTNAME');
 $timezone = getenv('TZ');
 
-$template = 'debug.twig';
+$template = 'dashboard.twig';
 $template_data = [
   'log_lines' => getenv('LOG_LINES'),
   'vmail_df' => $vmail_df,

+ 29 - 0
data/web/admin/index.php

@@ -0,0 +1,29 @@
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
+  header('Location: /admin/dashboard');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
+
+
+$template = 'admin_index.twig';
+$template_data = [
+  'login_delay' => @$_SESSION['ldelay']
+];
+
+$js_minifier->add('/web/js/site/index.js');
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

+ 13 - 3
data/web/mailbox.php → data/web/admin/mailbox.php

@@ -1,10 +1,20 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
 
-if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
-  header('Location: /');
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
   exit();
 }
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
+  header('Location: /admin');
+  exit();
+}
+
 require_once $_SERVER['DOCUMENT_ROOT'] .  '/inc/header.inc.php';
 $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 
@@ -14,7 +24,7 @@ $js_minifier->add('/web/js/site/mailbox.js');
 $js_minifier->add('/web/js/presets/sieveMailbox.js');
 $js_minifier->add('/web/js/site/pwgen.js');
 
-$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
+$role = "admin";
 $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
 $allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';
 

+ 12 - 3
data/web/queue.php → data/web/admin/queue.php

@@ -1,8 +1,17 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
 
-if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
-  header('Location: /');
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
+  header('Location: /admin');
   exit();
 }
 
@@ -11,7 +20,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
 $js_minifier->add('/web/js/site/queue.js');
 $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
 
-$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
+$role = "admin";
 
 $template = 'queue.twig';
 $template_data = [

+ 11 - 2
data/web/admin.php → data/web/admin/system.php

@@ -1,8 +1,17 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
 
-if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
-  header('Location: /');
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
+  header('Location: /admin');
   exit();
 }
 

+ 0 - 3
data/web/css/site/admin.css

@@ -59,9 +59,6 @@ body.modal-open {
 .table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td {
   padding: 3px;
 }
-table tbody tr {
-  cursor: pointer;
-}
 table tbody tr td input[type="checkbox"] {
   cursor: pointer;
 }

+ 28 - 0
data/web/domainadmin/index.php

@@ -0,0 +1,28 @@
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
+  header('Location: /admin/dashboard');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
+
+$template = 'domainadmin_index.twig';
+$template_data = [
+  'login_delay' => @$_SESSION['ldelay'],
+];
+
+$js_minifier->add('/web/js/site/index.js');
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

+ 58 - 0
data/web/domainadmin/mailbox.php

@@ -0,0 +1,58 @@
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
+  header('Location: /admin/dashboard');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "domainadmin") {
+  header('Location: /domainadmin');
+  exit();
+}
+
+require_once $_SERVER['DOCUMENT_ROOT'] .  '/inc/header.inc.php';
+$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+
+
+
+$js_minifier->add('/web/js/site/mailbox.js');
+$js_minifier->add('/web/js/presets/sieveMailbox.js');
+$js_minifier->add('/web/js/site/pwgen.js');
+
+$role = "domainadmin";
+$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
+$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';
+
+// domains
+$domains = mailbox('get', 'domains');
+
+// mailboxes
+$mailboxes = [];
+foreach ($domains as $domain) {
+  foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) {
+    $mailboxes[] = $mailbox;
+  }
+}
+
+$template = 'mailbox.twig';
+$template_data = [
+  'acl' => $_SESSION['acl'],
+  'acl_json' => json_encode($_SESSION['acl']),
+  'role' => $role,
+  'is_dual' => $is_dual,
+  'allow_admin_email_login' => $allow_admin_email_login,
+  'global_filters' => mailbox('get', 'global_filter_details'),
+  'domains' => $domains,
+  'mailboxes' => $mailboxes,
+  'lang_mailbox' => json_encode($lang['mailbox']),
+  'lang_rl' => json_encode($lang['ratelimit']),
+  'lang_edit' => json_encode($lang['edit']),
+  'lang_datatables' => json_encode($lang['datatables']),
+];
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

+ 44 - 0
data/web/domainadmin/user.php

@@ -0,0 +1,44 @@
+<?php
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+
+  /*
+  / DOMAIN ADMIN
+  */
+
+  require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
+  $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
+  $tfa_data = get_tfa();
+  $fido2_data = fido2(array("action" => "get_friendly_names"));
+  $username = $_SESSION['mailcow_cc_username'];
+
+  $template = 'domainadmin.twig';
+  $template_data = [
+    'acl' => $_SESSION['acl'],
+    'acl_json' => json_encode($_SESSION['acl']),
+    'user_spam_score' => mailbox('get', 'spam_score', $username),
+    'tfa_data' => $tfa_data,
+    'fido2_data' => $fido2_data,
+    'lang_user' => json_encode($lang['user']),
+    'lang_datatables' => json_encode($lang['datatables']),
+  ];
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
+  header('Location: /admin/dashboard');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+  header('Location: /user');
+  exit();
+}
+else {
+  header('Location: /domainadmin');
+  exit();
+}
+
+$js_minifier->add('/web/js/site/user.js');
+$js_minifier->add('/web/js/site/pwgen.js');
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

+ 1 - 0
data/web/inc/functions.inc.php

@@ -3101,6 +3101,7 @@ function clear_session(){
   session_write_close();
 }
 function set_user_loggedin_session($user) {
+  session_regenerate_id(true);
   $_SESSION['mailcow_cc_username'] = $user;
   $_SESSION['mailcow_cc_role'] = 'user';
   $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");

+ 1 - 1
data/web/inc/init_db.inc.php

@@ -3,7 +3,7 @@ function init_db_schema() {
   try {
     global $pdo;
 
-    $db_version = "20112024_1105";
+    $db_version = "27012025_1555";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));

+ 1 - 1
data/web/inc/prerequisites.inc.php

@@ -297,7 +297,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rspamd.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';
-require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.global.inc.php';
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/twig.inc.php';
 init_db_schema();
 if (isset($_SESSION['mailcow_cc_role'])) {

+ 17 - 2
data/web/inc/sessions.inc.php

@@ -99,15 +99,30 @@ if (isset($_POST["logout"])) {
     unset($_SESSION['sogo-sso-user-allowed']);
     unset($_SESSION['sogo-sso-pass']);
     unset($_SESSION["dual-login"]);
-    header("Location: /mailbox");
+    if ($_SESSION["mailcow_cc_role"] == "admin"){
+      header("Location: /admin/mailbox");
+    } elseif ($_SESSION["mailcow_cc_role"] == "domainadmin") {
+      header("Location: /domainadmin/mailbox");
+    } else {
+      header("Location: /");
+    }
     exit();
   }
   else {
+    $role = $_SESSION["mailcow_cc_role"];
     session_regenerate_id(true);
     session_unset();
     session_destroy();
     session_write_close();
-    header("Location: /");
+    if ($role == "admin") {
+      header("Location: /admin");
+    }
+    elseif ($role == "domainadmin") {
+      header("Location: /domainadmin");
+    }
+    else {
+      header("Location: /");
+    }
   }
 }
 

+ 93 - 0
data/web/inc/triggers.admin.inc.php

@@ -0,0 +1,93 @@
+<?php
+
+if (isset($_POST["verify_tfa_login"])) {
+  if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
+    if ($_SESSION['pending_mailcow_cc_role'] == "admin") {
+      $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
+      $_SESSION['mailcow_cc_role'] = "admin";
+      unset($_SESSION['pending_mailcow_cc_username']);
+      unset($_SESSION['pending_mailcow_cc_role']);
+      unset($_SESSION['pending_tfa_methods']);
+
+		  header("Location: /admin/dashboard");
+      die();
+    }
+  }
+
+  unset($_SESSION['pending_mailcow_cc_username']);
+  unset($_SESSION['pending_mailcow_cc_role']);
+  unset($_SESSION['pending_tfa_methods']);
+}
+
+if (isset($_GET["cancel_tfa_login"])) {
+  unset($_SESSION['pending_pw_reset_token']);
+  unset($_SESSION['pending_pw_new_password']);
+  unset($_SESSION['pending_mailcow_cc_username']);
+  unset($_SESSION['pending_mailcow_cc_role']);
+  unset($_SESSION['pending_tfa_methods']);
+
+  header("Location: /admin");
+}
+
+if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
+  $login_user = strtolower(trim($_POST["login_user"]));
+  $as = check_login($login_user, $_POST["pass_user"], false, array("role" => "admin"));
+
+  if ($as == "admin") {
+    session_regenerate_id(true);
+		$_SESSION['mailcow_cc_username'] = $login_user;
+		$_SESSION['mailcow_cc_role'] = "admin";
+		header("Location: /admin/dashboard");
+    die();
+	}
+	elseif ($as != "pending") {
+    unset($_SESSION['pending_mailcow_cc_username']);
+    unset($_SESSION['pending_mailcow_cc_role']);
+    unset($_SESSION['pending_tfa_methods']);
+		unset($_SESSION['mailcow_cc_username']);
+		unset($_SESSION['mailcow_cc_role']);
+	}
+}
+
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin" && !isset($_SESSION['mailcow_cc_api'])) {
+  // TODO: Move file upload to API?
+	if (isset($_POST["submit_main_logo"])) {
+    if ($_FILES['main_logo']['error'] == 0) {
+      customize('add', 'main_logo', $_FILES);
+    }
+    if ($_FILES['main_logo_dark']['error'] == 0) {
+      customize('add', 'main_logo_dark', $_FILES);
+    }
+	}
+	if (isset($_POST["reset_main_logo"])) {
+    customize('delete', 'main_logo');
+    customize('delete', 'main_logo_dark');
+	}
+  // Some actions will not be available via API
+	if (isset($_POST["license_validate_now"])) {
+		license('verify');
+	}
+  if (isset($_POST["admin_api"])) {
+    if (isset($_POST["admin_api"]["ro"])) {
+      admin_api('ro', 'edit', $_POST);
+    }
+    elseif (isset($_POST["admin_api"]["rw"])) {
+      admin_api('rw', 'edit', $_POST);
+    }
+	}
+  if (isset($_POST["admin_api_regen_key"])) {
+    if (isset($_POST["admin_api_regen_key"]["ro"])) {
+      admin_api('ro', 'regen_key', $_POST);
+    }
+    elseif (isset($_POST["admin_api_regen_key"]["rw"])) {
+      admin_api('rw', 'regen_key', $_POST);
+    }
+	}
+	if (isset($_POST["rspamd_ui"])) {
+		rspamd_ui('edit', $_POST);
+	}
+	if (isset($_POST["mass_send"])) {
+		sys_mail($_POST);
+	}
+}
+?>

+ 62 - 0
data/web/inc/triggers.domainadmin.inc.php

@@ -0,0 +1,62 @@
+<?php
+// SSO Domain Admin
+if (!empty($_GET['sso_token'])) {
+  $username = domain_admin_sso('check', $_GET['sso_token']);
+
+  if ($username !== false) {
+    session_regenerate_id(true);
+    $_SESSION['mailcow_cc_username'] = $username;
+    $_SESSION['mailcow_cc_role'] = 'domainadmin';
+    header('Location: /domainadmin/mailbox');
+  }
+}
+
+if (isset($_POST["verify_tfa_login"])) {
+  if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
+    if ($_SESSION['pending_mailcow_cc_role'] == "domainadmin") {
+      $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
+      $_SESSION['mailcow_cc_role'] = "domainadmin";
+      unset($_SESSION['pending_mailcow_cc_username']);
+      unset($_SESSION['pending_mailcow_cc_role']);
+      unset($_SESSION['pending_tfa_methods']);
+
+		  header("Location: /domainadmin/mailbox");
+      die();
+    }
+  }
+
+  unset($_SESSION['pending_mailcow_cc_username']);
+  unset($_SESSION['pending_mailcow_cc_role']);
+  unset($_SESSION['pending_tfa_methods']);
+}
+
+if (isset($_GET["cancel_tfa_login"])) {
+  unset($_SESSION['pending_pw_reset_token']);
+  unset($_SESSION['pending_pw_new_password']);
+  unset($_SESSION['pending_mailcow_cc_username']);
+  unset($_SESSION['pending_mailcow_cc_role']);
+  unset($_SESSION['pending_tfa_methods']);
+
+  header("Location: /domainadmin");
+}
+
+if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
+  $login_user = strtolower(trim($_POST["login_user"]));
+  $as = check_login($login_user, $_POST["pass_user"], false, array("role" => "domain_admin"));
+
+  if ($as == "domainadmin") {
+    session_regenerate_id(true);
+		$_SESSION['mailcow_cc_username'] = $login_user;
+		$_SESSION['mailcow_cc_role'] = "domainadmin";
+		header("Location: /domainadmin/mailbox");
+    die();
+	}
+	elseif ($as != "pending") {
+    unset($_SESSION['pending_mailcow_cc_username']);
+    unset($_SESSION['pending_mailcow_cc_role']);
+    unset($_SESSION['pending_tfa_methods']);
+		unset($_SESSION['mailcow_cc_username']);
+		unset($_SESSION['mailcow_cc_role']);
+	}
+}
+?>

+ 48 - 0
data/web/inc/triggers.global.inc.php

@@ -0,0 +1,48 @@
+<?php
+if (isset($_POST["quick_release"])) {
+	quarantine('quick_release', $_POST["quick_release"]);
+}
+
+if (isset($_POST["quick_delete"])) {
+	quarantine('quick_delete', $_POST["quick_delete"]);
+}
+
+if (isset($_SESSION['mailcow_cc_role']) && (isset($_SESSION['acl']['login_as']) && $_SESSION['acl']['login_as'] == "1")) {
+	if (isset($_GET["duallogin"])) {
+    $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
+    if (!$is_dual) {
+      $duallogin = html_entity_decode(rawurldecode($_GET["duallogin"]));
+      if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) {
+        if (!empty(mailbox('get', 'mailbox_details', $duallogin))) {
+          $_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username'];
+          $_SESSION["dual-login"]["role"]     = $_SESSION['mailcow_cc_role'];
+          $_SESSION['mailcow_cc_username']    = $duallogin;
+          $_SESSION['mailcow_cc_role']        = "user";
+          header("Location: /user");
+        }
+      }
+      else {
+        if (!empty(domain_admin('details', $duallogin))) {
+          $_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username'];
+          $_SESSION["dual-login"]["role"]     = $_SESSION['mailcow_cc_role'];
+          $_SESSION['mailcow_cc_username']    = $duallogin;
+          $_SESSION['mailcow_cc_role']        = "domainadmin";
+          header("Location: /user");
+        }
+      }
+    }
+  }
+}
+
+if (isset($_SESSION['mailcow_cc_role'])) {
+	if (isset($_POST["set_tfa"])) {
+		set_tfa($_POST);
+	}
+	if (isset($_POST["unset_tfa_key"])) {
+		unset_tfa_key($_POST);
+	}
+	if (isset($_POST["unset_fido2_key"])) {
+		fido2(array("action" => "unset_fido2_key", "post_data" => $_POST));
+	}
+}
+?>

+ 0 - 263
data/web/inc/triggers.inc.php

@@ -1,263 +0,0 @@
-<?php
-// handle iam authentication
-if ($iam_provider){
-  if (isset($_GET['iam_sso'])){
-    // redirect for sso
-    $redirect_uri = identity_provider('get-redirect');
-    $redirect_uri = !empty($redirect_uri) ? $redirect_uri : '/';
-    header('Location: ' . $redirect_uri);
-    die();
-  }
-  if ($_SESSION['iam_token'] && $_SESSION['iam_refresh_token']) {
-    // Session found, try to refresh
-    $isRefreshed = identity_provider('refresh-token');
-
-    if (!$isRefreshed){
-      // Session could not be refreshed, redirect to provider
-      $redirect_uri = identity_provider('get-redirect');
-      $redirect_uri = !empty($redirect_uri) ? $redirect_uri : '/';
-      header('Location: ' . $redirect_uri);
-      die();
-    }
-  } elseif ($_GET['code'] && $_GET['state'] === $_SESSION['oauth2state']) {
-    // Check given state against previously stored one to mitigate CSRF attack
-    // Recieved access token in $_GET['code']
-    // extract info and verify user
-    identity_provider('verify-sso');
-  }
-}
-
-// SSO Domain Admin
-if (!empty($_GET['sso_token'])) {
-  $username = domain_admin_sso('check', $_GET['sso_token']);
-
-  if ($username !== false) {
-    $_SESSION['mailcow_cc_username'] = $username;
-    $_SESSION['mailcow_cc_role'] = 'domainadmin';
-    header('Location: /mailbox');
-  }
-}
-
-if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) {
-  reset_password("issue", $_POST['username']);
-  header("Location: /");
-  exit;
-}
-if (isset($_POST["pw_reset"])) {
-  $username = reset_password("check", $_POST['token']);
-  $reset_result = reset_password("reset", array(
-    'new_password' => $_POST['new_password'],
-    'new_password2' => $_POST['new_password2'],
-    'token' => $_POST['token'],
-    'username' => $username,
-    'check_tfa' => True
-  ));
-
-  if ($reset_result){
-    header("Location: /");
-    exit;
-  }
-}
-if (isset($_POST["verify_tfa_login"])) {
-  if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
-    if ($_SESSION['pending_mailcow_cc_role'] == "admin") {
-      $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
-      $_SESSION['mailcow_cc_role'] = "admin";
-      unset($_SESSION['pending_mailcow_cc_username']);
-      unset($_SESSION['pending_mailcow_cc_role']);
-      unset($_SESSION['pending_tfa_methods']);
-
-		  header("Location: /debug");
-      die();
-    }
-    elseif ($_SESSION['pending_mailcow_cc_role'] == "domainadmin") {
-      $_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
-      $_SESSION['mailcow_cc_role'] = "domainadmin";
-      unset($_SESSION['pending_mailcow_cc_username']);
-      unset($_SESSION['pending_mailcow_cc_role']);
-      unset($_SESSION['pending_tfa_methods']);
-
-		  header("Location: /mailbox");
-      die();
-    }
-    elseif ($_SESSION['pending_mailcow_cc_role'] == "user") {
-      if (isset($_SESSION['pending_pw_reset_token']) && isset($_SESSION['pending_pw_new_password'])) {
-        reset_password("reset", array(
-          'new_password' => $_SESSION['pending_pw_new_password'],
-          'new_password2' => $_SESSION['pending_pw_new_password'],
-          'token' => $_SESSION['pending_pw_reset_token'],
-          'username' => $_SESSION['pending_mailcow_cc_username']
-        ));
-        unset($_SESSION['pending_pw_reset_token']);
-        unset($_SESSION['pending_pw_new_password']);
-        unset($_SESSION['pending_mailcow_cc_username']);
-        unset($_SESSION['pending_tfa_methods']);
-
-        header("Location: /");
-        die();
-      } else {
-        set_user_loggedin_session($_SESSION['pending_mailcow_cc_username']);
-        $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
-        $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
-        if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
-          header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
-          die();
-        } else {
-          header("Location: /user");
-          die();
-        }
-      }
-    }
-  }
-
-  unset($_SESSION['pending_mailcow_cc_username']);
-  unset($_SESSION['pending_mailcow_cc_role']);
-  unset($_SESSION['pending_tfa_methods']);
-}
-
-if (isset($_GET["cancel_tfa_login"])) {
-  unset($_SESSION['pending_pw_reset_token']);
-  unset($_SESSION['pending_pw_new_password']);
-  unset($_SESSION['pending_mailcow_cc_username']);
-  unset($_SESSION['pending_mailcow_cc_role']);
-  unset($_SESSION['pending_tfa_methods']);
-
-  header("Location: /");
-}
-
-if (isset($_POST["quick_release"])) {
-	quarantine('quick_release', $_POST["quick_release"]);
-}
-
-if (isset($_POST["quick_delete"])) {
-	quarantine('quick_delete', $_POST["quick_delete"]);
-}
-
-if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
-  $login_user = strtolower(trim($_POST["login_user"]));
-  $as = check_login($login_user, $_POST["pass_user"]);
-
-  if ($as == "admin") {
-		$_SESSION['mailcow_cc_username'] = $login_user;
-		$_SESSION['mailcow_cc_role'] = "admin";
-		header("Location: /debug");
-    die();
-	}
-	elseif ($as == "domainadmin") {
-		$_SESSION['mailcow_cc_username'] = $login_user;
-		$_SESSION['mailcow_cc_role'] = "domainadmin";
-		header("Location: /mailbox");
-    die();
-	}
-	elseif ($as == "user") {
-    set_user_loggedin_session($login_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();
-    }
-
-    $user_details = mailbox("get", "mailbox_details", $login_user);
-    $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
-    if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
-      header("Location: /SOGo/so/{$login_user}");
-      die();
-    } else {
-      header("Location: /user");
-      die();
-    }
-	}
-	elseif ($as != "pending") {
-    unset($_SESSION['pending_mailcow_cc_username']);
-    unset($_SESSION['pending_mailcow_cc_role']);
-    unset($_SESSION['pending_tfa_methods']);
-		unset($_SESSION['mailcow_cc_username']);
-		unset($_SESSION['mailcow_cc_role']);
-	}
-}
-
-if (isset($_SESSION['mailcow_cc_role']) && (isset($_SESSION['acl']['login_as']) && $_SESSION['acl']['login_as'] == "1")) {
-	if (isset($_GET["duallogin"])) {
-    $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
-    if (!$is_dual) {
-      $duallogin = html_entity_decode(rawurldecode($_GET["duallogin"]));
-      if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) {
-        if (!empty(mailbox('get', 'mailbox_details', $duallogin))) {
-          $_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username'];
-          $_SESSION["dual-login"]["role"]     = $_SESSION['mailcow_cc_role'];
-          $_SESSION['mailcow_cc_username']    = $duallogin;
-          $_SESSION['mailcow_cc_role']        = "user";
-          header("Location: /user");
-        }
-      }
-      else {
-        if (!empty(domain_admin('details', $duallogin))) {
-          $_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username'];
-          $_SESSION["dual-login"]["role"]     = $_SESSION['mailcow_cc_role'];
-          $_SESSION['mailcow_cc_username']    = $duallogin;
-          $_SESSION['mailcow_cc_role']        = "domainadmin";
-          header("Location: /user");
-        }
-      }
-    }
-  }
-}
-
-if (isset($_SESSION['mailcow_cc_role'])) {
-	if (isset($_POST["set_tfa"])) {
-		set_tfa($_POST);
-	}
-	if (isset($_POST["unset_tfa_key"])) {
-		unset_tfa_key($_POST);
-	}
-	if (isset($_POST["unset_fido2_key"])) {
-		fido2(array("action" => "unset_fido2_key", "post_data" => $_POST));
-	}
-}
-if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin" && !isset($_SESSION['mailcow_cc_api'])) {
-  // TODO: Move file upload to API?
-	if (isset($_POST["submit_main_logo"])) {
-    if ($_FILES['main_logo']['error'] == 0) {
-      customize('add', 'main_logo', $_FILES);
-    }
-    if ($_FILES['main_logo_dark']['error'] == 0) {
-      customize('add', 'main_logo_dark', $_FILES);
-    }
-	}
-	if (isset($_POST["reset_main_logo"])) {
-    customize('delete', 'main_logo');
-    customize('delete', 'main_logo_dark');
-	}
-  // Some actions will not be available via API
-	if (isset($_POST["license_validate_now"])) {
-		license('verify');
-	}
-  if (isset($_POST["admin_api"])) {
-    if (isset($_POST["admin_api"]["ro"])) {
-      admin_api('ro', 'edit', $_POST);
-    }
-    elseif (isset($_POST["admin_api"]["rw"])) {
-      admin_api('rw', 'edit', $_POST);
-    }
-	}
-  if (isset($_POST["admin_api_regen_key"])) {
-    if (isset($_POST["admin_api_regen_key"]["ro"])) {
-      admin_api('ro', 'regen_key', $_POST);
-    }
-    elseif (isset($_POST["admin_api_regen_key"]["rw"])) {
-      admin_api('rw', 'regen_key', $_POST);
-    }
-	}
-	if (isset($_POST["rspamd_ui"])) {
-		rspamd_ui('edit', $_POST);
-	}
-	if (isset($_POST["mass_send"])) {
-		sys_mail($_POST);
-	}
-}
-?>

+ 132 - 0
data/web/inc/triggers.user.inc.php

@@ -0,0 +1,132 @@
+<?php
+// handle iam authentication
+if ($iam_provider){
+  if (isset($_GET['iam_sso'])){
+    // redirect for sso
+    $redirect_uri = identity_provider('get-redirect');
+    $redirect_uri = !empty($redirect_uri) ? $redirect_uri : '/';
+    header('Location: ' . $redirect_uri);
+    die();
+  }
+  if ($_SESSION['iam_token'] && $_SESSION['iam_refresh_token']) {
+    // Session found, try to refresh
+    $isRefreshed = identity_provider('refresh-token');
+
+    if (!$isRefreshed){
+      // Session could not be refreshed, redirect to provider
+      $redirect_uri = identity_provider('get-redirect');
+      $redirect_uri = !empty($redirect_uri) ? $redirect_uri : '/';
+      header('Location: ' . $redirect_uri);
+      die();
+    }
+  } elseif ($_GET['code'] && $_GET['state'] === $_SESSION['oauth2state']) {
+    // Check given state against previously stored one to mitigate CSRF attack
+    // Recieved access token in $_GET['code']
+    // extract info and verify user
+    identity_provider('verify-sso');
+  }
+}
+
+if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) {
+  reset_password("issue", $_POST['username']);
+  header("Location: /");
+  exit;
+}
+if (isset($_POST["pw_reset"])) {
+  $username = reset_password("check", $_POST['token']);
+  $reset_result = reset_password("reset", array(
+    'new_password' => $_POST['new_password'],
+    'new_password2' => $_POST['new_password2'],
+    'token' => $_POST['token'],
+    'username' => $username,
+    'check_tfa' => True
+  ));
+
+  if ($reset_result){
+    header("Location: /");
+    exit;
+  }
+}
+if (isset($_POST["verify_tfa_login"])) {
+  if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
+    if ($_SESSION['pending_mailcow_cc_role'] == "user") {
+      if (isset($_SESSION['pending_pw_reset_token']) && isset($_SESSION['pending_pw_new_password'])) {
+        reset_password("reset", array(
+          'new_password' => $_SESSION['pending_pw_new_password'],
+          'new_password2' => $_SESSION['pending_pw_new_password'],
+          'token' => $_SESSION['pending_pw_reset_token'],
+          'username' => $_SESSION['pending_mailcow_cc_username']
+        ));
+        unset($_SESSION['pending_pw_reset_token']);
+        unset($_SESSION['pending_pw_new_password']);
+        unset($_SESSION['pending_mailcow_cc_username']);
+        unset($_SESSION['pending_tfa_methods']);
+
+        header("Location: /");
+        die();
+      } else {
+        set_user_loggedin_session($_SESSION['pending_mailcow_cc_username']);
+        $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
+        $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
+        if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
+          header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
+          die();
+        } else {
+          header("Location: /user");
+          die();
+        }
+      }
+    }
+  }
+
+  unset($_SESSION['pending_mailcow_cc_username']);
+  unset($_SESSION['pending_mailcow_cc_role']);
+  unset($_SESSION['pending_tfa_methods']);
+}
+
+if (isset($_GET["cancel_tfa_login"])) {
+  unset($_SESSION['pending_pw_reset_token']);
+  unset($_SESSION['pending_pw_new_password']);
+  unset($_SESSION['pending_mailcow_cc_username']);
+  unset($_SESSION['pending_mailcow_cc_role']);
+  unset($_SESSION['pending_tfa_methods']);
+
+  header("Location: /");
+}
+
+if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
+  $login_user = strtolower(trim($_POST["login_user"]));
+  $as = check_login($login_user, $_POST["pass_user"], false, array("role" => "user"));
+
+  if ($as == "user") {
+    set_user_loggedin_session($login_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();
+    }
+
+    $user_details = mailbox("get", "mailbox_details", $login_user);
+    $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
+    if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
+      header("Location: /SOGo/so/{$login_user}");
+      die();
+    } else {
+      header("Location: /user");
+      die();
+    }
+	}
+	elseif ($as != "pending") {
+    unset($_SESSION['pending_mailcow_cc_username']);
+    unset($_SESSION['pending_mailcow_cc_role']);
+    unset($_SESSION['pending_tfa_methods']);
+		unset($_SESSION['mailcow_cc_username']);
+		unset($_SESSION['mailcow_cc_role']);
+	}
+}
+?>

+ 10 - 9
data/web/index.php

@@ -1,5 +1,6 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.user.inc.php';
 
 if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) {
   $oauth2_request = $_SESSION['oauth2_request'];
@@ -7,14 +8,6 @@ if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) {
   header('Location: ' . $oauth2_request);
   exit();
 }
-elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
-  header('Location: /debug');
-  exit();
-}
-elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
-  header('Location: /mailbox');
-  exit();
-}
 elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
   $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
   $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
@@ -25,6 +18,14 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
   }
   exit();
 }
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
+  header('Location: /admin/dashboard');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
 
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
 $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
@@ -36,7 +37,7 @@ if ($iam_provider){
 }
 
 
-$template = 'index.twig';
+$template = 'user_index.twig';
 $template_data = [
   'oauth2_request' => @$_SESSION['oauth2_request'],
   'is_mobileconfig' => str_contains($_SESSION['index_query_string'], 'mobileconfig'),

+ 0 - 0
data/web/js/site/debug.js → data/web/js/site/dashboard.js


+ 1 - 1
data/web/js/site/mailbox.js

@@ -939,7 +939,7 @@ jQuery(function($){
               '<a href="#" data-action="delete_selected" data-id="single-mailbox" data-api-url="delete/mailbox" data-item="' + encodeURIComponent(item.username) + '" class="btn btn-sm btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
               '<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-lg btn-xs-half btn-success"><i class="bi bi-person-fill"></i> Login</a>';
               if (ALLOW_ADMIN_EMAIL_LOGIN) {
-                item.action += '<a href="/sogo-auth.php?login=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-lg btn-xs-half btn-primary" target="_blank"><i class="bi bi-envelope-fill"></i> SOGo</a>';
+                item.action += '<a href="/sogo-auth.php?login=' + encodeURIComponent(item.username) + '" class="login_as btn btn-sm btn-xs-lg btn-xs-half btn-primary"><i class="bi bi-envelope-fill"></i> SOGo</a>';
               }
               item.action += '</div>';
             }

+ 2 - 2
data/web/reset-password.php

@@ -2,11 +2,11 @@
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
 
 if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
-  header('Location: /debug');
+  header('Location: /admin/dashboard');
   exit();
 }
 elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
-  header('Location: /mailbox');
+  header('Location: /domainadmin/mailbox');
   exit();
 }
 elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {

+ 91 - 0
data/web/templates/admin_index.twig

@@ -0,0 +1,91 @@
+{% extends 'base.twig' %}
+
+{% block navbar %}{% endblock %}
+
+{% block content %}
+<div class="row mb-4" style="margin-top: 60px">
+  <div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto">
+    <div class="card">
+      <div class="card-header d-flex align-items-center">
+        <i class="bi bi-person-fill me-2"></i> {{ lang.login.login }}
+        <div class="ms-auto form-check form-switch my-auto d-flex align-items-center">
+          <label class="form-check-label"><i class="bi bi-moon-fill"></i></label>
+          <input class="form-check-input ms-2" type="checkbox" id="dark-mode-toggle">
+        </div>
+      </div>
+      <div class="card-body">
+        <div class="text-center mailcow-logo mb-4">
+          <img class="main-logo" src="{{ logo|default('/img/cow_mailcow.svg') }}" alt="mailcow">
+          <img class="main-logo-dark" src="{{ logo_dark|default('/img/cow_mailcow.svg') }}" alt="mailcow-logo-dark">
+        </div>
+        {% if ui_texts.ui_announcement_text and ui_texts.ui_announcement_active %}
+        <div class="my-4 alert alert-{{ ui_texts.ui_announcement_type }} rot-enc ui-announcement-alert">{{ ui_texts.ui_announcement_text|rot13 }}</div>
+        {% endif %}
+        <form method="post" autofill="off">
+          <div class="d-flex mt-3">
+            <label class="visually-hidden" for="login_user">{{ lang.login.username }}</label>
+            <div class="input-group">
+              <div class="input-group-text"><i class="bi bi-person-fill"></i></div>
+              <input name="login_user" autocorrect="off" autocapitalize="none" type="text" id="login_user" class="form-control" placeholder="{{ lang.login.username }}" required="" autofocus="" autocomplete="username">
+            </div>
+          </div>
+          <div class="d-flex mt-3">
+            <label class="visually-hidden" for="pass_user">{{ lang.login.password }}</label>
+            <div class="input-group">
+              <div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
+              <input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
+            </div>
+          </div>
+          <div class="d-flex justify-content-between mt-4" style="position: relative">
+            <button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>            <div class="d-grid d-sm-block">
+            <button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-secondary ms-auto dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+              <span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span>
+            </button>
+            <ul class="dropdown-menu ms-auto login">
+              {% for key, val in available_languages %}
+                <li>
+                  <a class="dropdown-item {% if mailcow_locale == key %}active{% endif %}" href="?{{ query_string({'lang': key}) }}">
+                    <span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
+                  </a>
+                </li>
+              {% endfor %}
+            </ul>
+            </div>
+          </div>
+        </form>
+        <div class="hr-title mt-5"><strong>{{ lang.login.other_logins }}</strong></div>
+        <div class="d-flex flex-column align-items-center">
+          <a class="btn btn-xs-lg btn-secondary w-100" style="max-width: 400px;" href="#" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</a>
+        </div>
+        {% if login_delay %}
+        <p><div class="my-4 alert alert-info">{{ lang.login.delayed|format(login_delay) }}</b></div></p>
+        {% endif %}
+        <div class="my-4" id="fido2-alerts"></div>
+        {% if (mailcow_apps or app_links) and not hide_mailcow_apps %}
+        <legend><i class="bi bi-link-45deg"></i> {{ ui_texts.apps_name|raw }}</legend><hr />
+        <div class="my-2 d-grid gap-2 d-sm-block apps">
+          {% for app in mailcow_apps %}
+            {% if not app.hide %}
+              {% if not skip_sogo or not is_uri('SOGo', app.link) %}
+              <div class="m-2">
+                <a href="{{ app.link }}" role="button" {% if app.description %}title="{{ app.description }}"{% endif %} class="btn btn-primary btn-block">{{ app.name }}</a>
+              </div>
+            {% endif %}
+          {% endif %}
+          {% endfor %}
+          {% for row in app_links %}
+            {% for key, val in row %}
+            {% if not val.hide %}
+              <div class="m-2">
+                <a href="{{ val.link }}" role="button" class="btn btn-primary btn-block">{{ key }}</a>
+              </div>
+            {% endif %}
+            {% endfor %}
+          {% endfor %}
+        </div>
+        {% endif %}
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock %}

+ 26 - 17
data/web/templates/base.twig

@@ -66,27 +66,36 @@
         <li class="nav-item dropdown">
           <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-expanded="false">{{ lang.header.mailcow_system }}</a>
           <ul class="dropdown-menu">
-            <li><a href="/debug" class="dropdown-item {% if is_uri('debug') %}active{% endif %}">{{ lang.header.debug }}</a></li>
-            <li><a href="/admin" class="dropdown-item {% if is_uri('admin') %}active{% endif %}">{{ lang.header.mailcow_config }}</a></li>
+            <li><a href="/admin/dashboard" class="dropdown-item {% if is_uri('dashboard') %}active{% endif %}">{{ lang.header.debug }}</a></li>
+            <li><a href="/admin/system" class="dropdown-item {% if is_uri('system') %}active{% endif %}">{{ lang.header.mailcow_config }}</a></li>
+          </ul>
+        </li>
+        <li class="nav-item dropdown">
+          <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-expanded="false">{{ lang.header.email }}</a>
+          <ul class="dropdown-menu">
+            <li><a href="/admin/mailbox" class="dropdown-item {% if is_uri('mailbox') %}active{% endif %}">{{ lang.header.mailcow_config }}</a></li>
+            <li><a href="/quarantine" class="dropdown-item {% if is_uri('quarantine') %}active{% endif %}">{{ lang.header.quarantine }}</a></li>
+            <li><a href="/admin/queue" class="dropdown-item {% if is_uri('queue') %}active{% endif %}">{{ lang.queue.queue_manager }}</a></li>
+            <li><a href="#" class="dropdown-item" data-bs-toggle="modal" data-container="sogo-mailcow" data-bs-target="#RestartContainer">{{ lang.header.restart_sogo }}</a></li>
           </ul>
         </li>
         {% endif %}
-        {% if mailcow_cc_role != 'admin' %}
+        {% if mailcow_cc_role == 'domainadmin' %}
+        <li class="nav-item dropdown">
+          <a href="/domainadmin/user" class="nav-link" role="button" aria-expanded="false">{{ lang.header.user_settings }}</a>
+        </li>
+        {% elseif mailcow_cc_role == 'user' %}
         <li class="nav-item dropdown">
           <a href="/user" class="nav-link" role="button" aria-expanded="false">{{ lang.header.user_settings }}</a>
         </li>
         {% endif %}
 
-        {% if mailcow_cc_role == 'admin' or mailcow_cc_role == 'domainadmin' %}
+        {% if mailcow_cc_role == 'domainadmin' %}
         <li class="nav-item dropdown">
           <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" role="button" aria-expanded="false">{{ lang.header.email }}</a>
           <ul class="dropdown-menu">
-            <li><a href="/mailbox" class="dropdown-item {% if is_uri('mailbox') %}active{% endif %}">{{ lang.header.mailcow_config }}</a></li>
+            <li><a href="/domainadmin/mailbox" class="dropdown-item {% if is_uri('mailbox') %}active{% endif %}">{{ lang.header.mailcow_config }}</a></li>
             <li><a href="/quarantine" class="dropdown-item {% if is_uri('quarantine') %}active{% endif %}">{{ lang.header.quarantine }}</a></li>
-            {% if mailcow_cc_role == 'admin' %}
-            <li><a href="/queue" class="dropdown-item {% if is_uri('queue') %}active{% endif %}">{{ lang.queue.queue_manager }}</a></li>
-            <li><a href="#" class="dropdown-item" data-bs-toggle="modal" data-container="sogo-mailcow" data-bs-target="#RestartContainer">{{ lang.header.restart_sogo }}</a></li>
-            {% endif %}
           </ul>
         </li>
         {% endif %}
@@ -246,7 +255,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
     $(".totp-authenticator-selection").click(function(){
       $(".totp-authenticator-selection").removeClass("active");
       $(this).addClass("active");
-      
+
       var id = $(this).children('input').first().val();
       $("#totp_selected_id").val(id);
 
@@ -255,7 +264,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
     if ($('.totp-authenticator-selection').length == 1 &&
         $('#pending_tfa_tab_yubi_otp').length == 0 &&
         $('.webauthn-authenticator-selection').length == 0){
-      
+
       // select default if only one authenticator exists
       $('.totp-authenticator-selection').addClass("active");
 
@@ -268,7 +277,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
     $('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
       // autofocus
       setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
-    });    
+    });
     // validate Yubi OTP tfa
     if ($('.webauthn-authenticator-selection').length == 0){
       // autofocus
@@ -287,10 +296,10 @@ function recursiveBase64StrToArrayBuffer(obj) {
     $(".webauthn-authenticator-selection").click(function(){
       $(".webauthn-authenticator-selection").removeClass("active");
       $(this).addClass("active");
-      
+
       var id = $(this).children('input').first().val();
       $("#webauthn_selected_id").val(id);
-      
+
       var webauthn_status_auth = document.getElementById('webauthn_status_auth');
       webauthn_status_auth.style.setProperty('display', 'flex', 'important');
       var webauthn_return_code = document.getElementById('webauthn_return_code');
@@ -313,7 +322,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
           console.log(json);
           if (json.success === false) throw new Error();
           if (json.type === "error") throw new Error(json.msg);
-      
+
           recursiveBase64StrToArrayBuffer(json);
           return json;
         }).then(getCredentialArgs => {
@@ -340,7 +349,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
           webauthn_return_code.style.setProperty('display', 'block', 'important');
           webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
         });
-      } 
+      }
     });
     $('#ConfirmTFAModal').on('hidden.bs.modal', function(){
       // cancel pending login
@@ -551,7 +560,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
         Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }}
     </a>
   </span>
-  {% endif %}  
+  {% endif %}
   {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
   <span class="version">
     🛠️🐮 + 🐋 = 💕

+ 2 - 2
data/web/templates/debug.twig → data/web/templates/dashboard.twig

@@ -42,8 +42,8 @@
                 <img class="main-logo-dark img-responsive my-auto m-auto" alt="mailcow-logo-dark" style="max-width: 85%; max-height: 85%;" src="{{ logo_dark|default('/img/cow_mailcow.svg') }}">
               </div>
               <div class="col-sm-12 col-md-8">
-                <div class="table-responsive" style="margin-top: 10px;">
-                  <table class="table table-striped table-condensed w-100">
+                <div style="margin-top: 10px;">
+                  <table class="table table-striped w-100">
                     <tbody>
                       <tr>
                         <td>Hostname</td>

+ 91 - 0
data/web/templates/domainadmin_index.twig

@@ -0,0 +1,91 @@
+{% extends 'base.twig' %}
+
+{% block navbar %}{% endblock %}
+
+{% block content %}
+<div class="row mb-4" style="margin-top: 60px">
+  <div class="col-12 col-md-7 col-lg-6 col-xl-5 ms-auto me-auto">
+    <div class="card">
+      <div class="card-header d-flex align-items-center">
+        <i class="bi bi-person-fill me-2"></i> {{ lang.login.login }}
+        <div class="ms-auto form-check form-switch my-auto d-flex align-items-center">
+          <label class="form-check-label"><i class="bi bi-moon-fill"></i></label>
+          <input class="form-check-input ms-2" type="checkbox" id="dark-mode-toggle">
+        </div>
+      </div>
+      <div class="card-body">
+        <div class="text-center mailcow-logo mb-4">
+          <img class="main-logo" src="{{ logo|default('/img/cow_mailcow.svg') }}" alt="mailcow">
+          <img class="main-logo-dark" src="{{ logo_dark|default('/img/cow_mailcow.svg') }}" alt="mailcow-logo-dark">
+        </div>
+        {% if ui_texts.ui_announcement_text and ui_texts.ui_announcement_active %}
+        <div class="my-4 alert alert-{{ ui_texts.ui_announcement_type }} rot-enc ui-announcement-alert">{{ ui_texts.ui_announcement_text|rot13 }}</div>
+        {% endif %}
+        <form method="post" autofill="off">
+          <div class="d-flex mt-3">
+            <label class="visually-hidden" for="login_user">{{ lang.login.username }}</label>
+            <div class="input-group">
+              <div class="input-group-text"><i class="bi bi-person-fill"></i></div>
+              <input name="login_user" autocorrect="off" autocapitalize="none" type="text" id="login_user" class="form-control" placeholder="{{ lang.login.username }}" required="" autofocus="" autocomplete="username">
+            </div>
+          </div>
+          <div class="d-flex mt-3">
+            <label class="visually-hidden" for="pass_user">{{ lang.login.password }}</label>
+            <div class="input-group">
+              <div class="input-group-text"><i class="bi bi-lock-fill"></i></div>
+              <input name="pass_user" type="password" id="pass_user" class="form-control" placeholder="{{ lang.login.password }}" required="" autocomplete="current-password">
+            </div>
+          </div>
+          <div class="d-flex justify-content-between mt-4" style="position: relative">
+            <button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>            <div class="d-grid d-sm-block">
+            <button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-secondary ms-auto dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+              <span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span>
+            </button>
+            <ul class="dropdown-menu ms-auto login">
+              {% for key, val in available_languages %}
+                <li>
+                  <a class="dropdown-item {% if mailcow_locale == key %}active{% endif %}" href="?{{ query_string({'lang': key}) }}">
+                    <span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
+                  </a>
+                </li>
+              {% endfor %}
+            </ul>
+            </div>
+          </div>
+        </form>
+        <div class="hr-title mt-5"><strong>{{ lang.login.other_logins }}</strong></div>
+        <div class="d-flex flex-column align-items-center">
+          <a class="btn btn-xs-lg btn-secondary w-100" style="max-width: 400px;" href="#" id="fido2-login"><i class="bi bi-shield-fill-check"></i> {{ lang.login.fido2_webauthn }}</a>
+        </div>
+        {% if login_delay %}
+        <p><div class="my-4 alert alert-info">{{ lang.login.delayed|format(login_delay) }}</b></div></p>
+        {% endif %}
+        <div class="my-4" id="fido2-alerts"></div>
+        {% if (mailcow_apps or app_links) and not hide_mailcow_apps %}
+        <legend><i class="bi bi-link-45deg"></i> {{ ui_texts.apps_name|raw }}</legend><hr />
+        <div class="my-2 d-grid gap-2 d-sm-block apps">
+          {% for app in mailcow_apps %}
+            {% if not app.hide %}
+              {% if not skip_sogo or not is_uri('SOGo', app.link) %}
+              <div class="m-2">
+                <a href="{{ app.link }}" role="button" {% if app.description %}title="{{ app.description }}"{% endif %} class="btn btn-primary btn-block">{{ app.name }}</a>
+              </div>
+            {% endif %}
+          {% endif %}
+          {% endfor %}
+          {% for row in app_links %}
+            {% for key, val in row %}
+            {% if not val.hide %}
+              <div class="m-2">
+                <a href="{{ val.link }}" role="button" class="btn btn-primary btn-block">{{ key }}</a>
+              </div>
+            {% endif %}
+            {% endfor %}
+          {% endfor %}
+        </div>
+        {% endif %}
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock %}

+ 2 - 2
data/web/templates/user/tab-user-auth.twig

@@ -20,11 +20,11 @@
                   {{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i>
                 </button>
               {% elseif dual_login %}
-                <a target="_blank" href="/sogo-auth.php?login={{ mailcow_cc_username  }}" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100">
+                <a href="/sogo-auth.php?login={{ mailcow_cc_username  }}" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100">
                   {{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i>
                 </a>
               {% else %}
-                <a target="_blank" href="/SOGo/so" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100">
+                <a href="/SOGo/so" role="button" class="btn btn-primary btn-lg btn-block btn-xs-lg w-100">
                   {{ lang.user.open_webmail_sso }} <i class="bi bi-arrow-right"></i>
                 </a>
               {% endif %}

+ 0 - 0
data/web/templates/index.twig → data/web/templates/user_index.twig


+ 10 - 23
data/web/user.php

@@ -1,29 +1,8 @@
 <?php
 require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
-if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.user.inc.php';
 
-  /*
-  / DOMAIN ADMIN
-  */
-
-  require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
-  $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
-  $tfa_data = get_tfa();
-  $fido2_data = fido2(array("action" => "get_friendly_names"));
-  $username = $_SESSION['mailcow_cc_username'];
-
-  $template = 'domainadmin.twig';
-  $template_data = [
-    'acl' => $_SESSION['acl'],
-    'acl_json' => json_encode($_SESSION['acl']),
-    'user_spam_score' => mailbox('get', 'spam_score', $username),
-    'tfa_data' => $tfa_data,
-    'fido2_data' => $fido2_data,
-    'lang_user' => json_encode($lang['user']),
-    'lang_datatables' => json_encode($lang['datatables']),
-  ];
-}
-elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
 
   /*
   / USER
@@ -95,6 +74,14 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
     'lang_datatables' => json_encode($lang['datatables']),
   ];
 }
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
+  header('Location: /admin/dashboard');
+  exit();
+}
+elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
+  header('Location: /domainadmin/mailbox');
+  exit();
+}
 else {
   header('Location: /');
   exit();