2
0
Эх сурвалжийг харах

Merge pull request #6268 from mailcow/feat/nightly-separated-login

[Web] Separate Login pages
FreddleSpl0it 7 сар өмнө
parent
commit
e2cf22ff9e

+ 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();
 }
 
@@ -15,7 +24,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');
@@ -59,7 +68,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';

+ 24 - 6
data/web/inc/functions.inc.php

@@ -2337,12 +2337,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
 
       switch ($_data['authsource']) {
         case 'keycloak':
-        case 'generic-oidc':
-          if ($_data['authsource'] == 'keycloak') {
-            $url = "{$_data['server_url']}/realms/{$_data['realm']}/protocol/openid-connect/token";
-          } else {
-            $url = $_data['token_url'];
-          }
+          $url = "{$_data['server_url']}/realms/{$_data['realm']}/protocol/openid-connect/token";
           $req = http_build_query(array(
             'grant_type'    => 'client_credentials',
             'client_id'     => $_data['client_id'],
@@ -2355,6 +2350,29 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
           curl_setopt($curl, CURLOPT_POSTFIELDS, $req);
           curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
           curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+          if ($_data['ignore_ssl_error'] == "1"){
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+          }
+          $res = curl_exec($curl);
+          $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+          curl_close ($curl);
+
+          if ($code != 200) {
+            return false;
+          }
+        break;
+        case 'generic-oidc':
+          $url = $_data['token_url'];
+          $curl = curl_init();
+          curl_setopt($curl, CURLOPT_URL, $url);
+          curl_setopt($curl, CURLOPT_TIMEOUT, 7);
+          curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+          curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
+          if ($_data['ignore_ssl_error'] == "1"){
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+          }
           $res = curl_exec($curl);
           $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
           curl_close ($curl);

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

@@ -4,7 +4,7 @@ function init_db_schema()
   try {
     global $pdo;
 
-    $db_version = "24012025_0923";
+    $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 - 272
data/web/inc/triggers.inc.php

@@ -1,272 +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_regenerate_id(true);
-    $_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_regenerate_id(true);
-		$_SESSION['mailcow_cc_username'] = $login_user;
-		$_SESSION['mailcow_cc_role'] = "admin";
-		header("Location: /debug");
-    die();
-	}
-	elseif ($as == "domainadmin") {
-    session_regenerate_id(true);
-		$_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();
-    }
-    if (!isset($_SESSION['oauth2_request'])) {
-      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']);
-	} else {
-    session_regenerate_id(true);
-  }
-}
-
-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

@@ -938,7 +938,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>';
             }

+ 38 - 0
data/web/lang/lang.de-de.json

@@ -206,6 +206,39 @@
         "help_text": "Hilfstext unter Login-Maske (HTML ist zulässig)",
         "host": "Host",
         "html": "HTML",
+        "iam": "Identity Provider",
+        "iam_attribute_field": "Attribut Feld",
+        "iam_authorize_url": "Authorization Endpunkt",
+        "iam_auth_flow": "Authentication Flow",
+        "iam_auth_flow_info": "Zusätzlich zum Authorization Code Flow (dem Standard-Flow in Keycloak), der für Single-Sign-On-Logins verwendet wird, unterstützt mailcow auch den Authentication Flow mit direkten Anmeldeinformationen. Der Mailpassword Flow versucht, die Anmeldedaten des Benutzers über die Keycloak Admin REST API zu validieren. Dabei ruft mailcow das gehashte Passwort aus dem <code>mailcow_password</code> Attribut ab, das in Keycloak zugewiesen ist.",
+        "iam_basedn": "Base DN",
+        "iam_client_id": "Client ID",
+        "iam_client_secret": "Client Secret",
+        "iam_client_scopes": "Client Scopes",
+        "iam_description": "Konfiguriere einen externen Identity Provider für die Authentifizierung<br>Die Mailboxen der Benutzer werden bei ihrer ersten Anmeldung automatisch erstellt, vorausgesetzt, dass ein Attribut Mapping festgelegt wurde.",
+        "iam_extra_permission": "Damit die folgenden Einstellungen funktionieren, benötigt der mailcow Client in Keycloak ein <code>Service-Konto</code> und die Berechtigung <code>view-users</code>.",
+        "iam_host": "Host",
+        "iam_host_info": "Gib einen oder mehrere LDAP-Hosts ein, getrennt durch Kommas.",
+        "iam_import_users": "Import Users",
+        "iam_mapping": "Attribut Mapping",
+        "iam_bindpass": "Bind Passwort",
+        "iam_periodic_full_sync": "Periodic Full Sync",
+        "iam_port": "Port",
+        "iam_realm": "Realm",
+        "iam_redirect_url": "Redirect Url",
+        "iam_rest_flow": "Mailpassword Flow",
+        "iam_server_url": "Server Url",
+        "iam_sso": "Single Sign-On",
+        "iam_sync_interval": "Sync / Import interval (min)",
+        "iam_test_connection": "Verbindung Testen",
+        "iam_token_url": "Token Endpunkt",
+        "iam_userinfo_url": "User info Endpunkt",
+        "iam_username_field": "Username Feld",
+        "iam_binddn": "Bind DN",
+        "iam_use_ssl": "Benutze SSL",
+        "iam_use_tls": "Benutze TLS",
+        "iam_version": "Version",
+        "ignore_ssl_error": "Ignoriere SSL Errors",
         "import": "Importieren",
         "import_private_key": "Private Key importieren",
         "in_use_by": "Verwendet von",
@@ -403,6 +436,7 @@
         "goto_empty": "Eine Alias-Adresse muss auf mindestens eine gültige Ziel-Adresse zeigen",
         "goto_invalid": "Ziel-Adresse %s ist ungültig",
         "ham_learn_error": "Ham Lernfehler: %s",
+        "iam_test_connection": "Verbindung fehlgeschlagen",
         "imagick_exception": "Fataler Bildverarbeitungsfehler",
         "img_dimensions_exceeded": "Grafik überschreitet die maximale Bildgröße",
         "img_invalid": "Grafik konnte nicht validiert werden",
@@ -766,6 +800,9 @@
         "forgot_password": "> Passwort vergessen?",
         "invalid_pass_reset_token": "Der Rücksetz-Token für das Passwort ist ungültig oder abgelaufen.<br>Bitte fordern Sie einen neuen Link zur Passwortwiederherstellung an.",
         "login": "Anmelden",
+        "login_user": "Benutzer Anmelden",
+        "login_dadmin": "Domain-Administrator Anmelden",
+        "login_admin": "Administrator Anmelden",
         "mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
         "new_password": "Neues Passwort",
         "new_password_confirm": "Neues Passwort bestätigen",
@@ -1077,6 +1114,7 @@
         "forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",
         "global_filter_written": "Filterdatei wurde erfolgreich geschrieben",
         "hash_deleted": "Hash wurde gelöscht",
+        "iam_test_connection": "Verbindung erfolgreich",
         "ip_check_opt_in_modified": "IP Check wurde erfolgreich gespeichert",
         "item_deleted": "Objekt %s wurde entfernt",
         "item_released": "Objekt %s freigegeben",

+ 3 - 0
data/web/lang/lang.en-gb.json

@@ -804,6 +804,9 @@
         "forgot_password": "> Forgot Password?",
         "invalid_pass_reset_token": "The reset password token is invalid or has expired.<br>Please request a new password reset link.",
         "login": "Login",
+        "login_user": "User Login",
+        "login_dadmin": "Domain-Administrator Login",
+        "login_admin": "Administrator Login",
         "mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
         "new_password": "New Password",
         "new_password_confirm": "Confirm new password",

+ 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') {

+ 4 - 4
data/web/templates/admin/tab-config-identity-provider.twig

@@ -84,7 +84,7 @@
             </div>
             <div class="col-12 col-md-9 col-lg-4">
               <div class="row px-2 align-items-center">
-                <span class="col-5 p-0 pe-2">Attribute</span>
+                <span class="col-5 p-0 pe-2">{{ lang.user.attribute }}</span>
                 <span class="col-5 p-0 pe-2">{{ lang.mailbox.template }}</span>
                 <div class="col-2 p-0 d-flex">
                   <button class="btn btn-sm d-block d-sm-inline btn-secondary ms-auto iam_rolemap_add_keycloak"><i class="bi bi-plus-lg"></i></button>
@@ -274,8 +274,8 @@
             </div>
             <div class="col-12 col-md-9 col-lg-4">
               <div class="row px-2 align-items-center">
-                <span class="col-5 p-0 pe-2">Attribute</span>
-                <span class="col-5 p-0 pe-2">Template</span>
+                <span class="col-5 p-0 pe-2">{{ lang.user.attribute }}</span>
+                <span class="col-5 p-0 pe-2">{{ lang.mailbox.template }}</span>
                 <div class="col-2 p-0 d-flex">
                   <button class="btn btn-sm d-block d-sm-inline btn-secondary ms-auto iam_rolemap_add_generic"><i class="bi bi-plus-lg"></i></button>
                 </div>
@@ -454,7 +454,7 @@
             </div>
             <div class="col-12 col-md-9 col-lg-4">
               <div class="row px-2 align-items-center">
-                <span class="col-5 p-0 pe-2">Attribute</span>
+                <span class="col-5 p-0 pe-2">{{ lang.user.attribute }}</span>
                 <span class="col-5 p-0 pe-2">{{ lang.mailbox.template }}</span>
                 <div class="col-2 p-0 d-flex">
                   <button class="btn btn-sm d-block d-sm-inline btn-secondary ms-auto iam_rolemap_add_ldap"><i class="bi bi-plus-lg"></i></button>

+ 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_admin }}
+        <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_dadmin }}
+        <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 %}

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

@@ -7,7 +7,7 @@
   <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 }}
+        <i class="bi bi-person-fill me-2"></i> {{ lang.login.login_user }}
         <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">

+ 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();