From 80f3325c99e4aca14d49d335c6b06eddf4d1a04b Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Thu, 29 Jan 2026 15:34:38 +1100 Subject: [PATCH 1/9] [SD-1482] Added text changes to the tfa overview page. --- .../tide_tfa/src/Form/TideTfaOverviewForm.php | 132 ++++++++++++++++++ .../Plugin/TfaSetup/TideTfaEmailOtpSetup.php | 2 +- .../src/Routing/TideTfaRouteSubscriber.php | 5 + modules/tide_tfa/tide_tfa.module | 15 ++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 modules/tide_tfa/src/Form/TideTfaOverviewForm.php diff --git a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php new file mode 100644 index 000000000..fd11fc8b5 --- /dev/null +++ b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php @@ -0,0 +1,132 @@ + 'markup', + '#markup' => '

' . $this->t('Multi-factor authentication provides + additional security for your account. With multi-factor authentication enabled, + you log in to the CMS with a verification code in addition to your username and + password.') . '

', + ]; + // $form_state['storage']['account'] = $user;. + $config = $this->config('tfa.settings'); + $user_tfa = $this->tfaGetTfaData($user->id(), $this->userData); + $enabled = isset($user_tfa['status']) && $user_tfa['status']; + + if ($config->get('enabled')) { + $enabled = isset($user_tfa['status'], $user_tfa['data']) && !empty($user_tfa['data']['plugins']) && $user_tfa['status']; + $enabled_plugins = $user_tfa['data']['plugins'] ?? []; + + $validation_plugins = $this->tfaValidation->getDefinitions(); + if ($validation_plugins) { + $output['validation'] = [ + '#type' => 'details', + '#title' => $this->t('Validation plugins'), + '#open' => TRUE, + ]; + + foreach ($validation_plugins as $plugin_id => $plugin) { + if (!empty($config->get('allowed_validation_plugins')[$plugin_id])) { + $output['validation'][$plugin_id] = $this->tfaPluginSetupFormOverview($plugin, $user, !empty($enabled_plugins[$plugin_id])); + } + } + } + + if ($enabled) { + $login_plugins = $this->tfaLogin->getDefinitions(); + if ($login_plugins) { + $output['login'] = [ + '#type' => 'details', + '#title' => $this->t('Login plugins'), + '#open' => TRUE, + '#access' => FALSE, + ]; + + foreach ($login_plugins as $plugin_id => $plugin) { + if (!empty($config->get('login_plugins')[$plugin_id])) { + $output['login'][$plugin_id] = $this->tfaPluginSetupFormOverview($plugin, $user, TRUE); + $output['login']['#access'] = TRUE; + } + } + } + + $send_plugins = $this->tfaSend->getDefinitions(); + if ($send_plugins) { + $output['send'] = [ + '#type' => 'details', + '#title' => $this->t('Send plugins'), + '#open' => TRUE, + ]; + + foreach ($send_plugins as $plugin_id => $plugin) { + if (!empty($config->get('send_plugins')[$plugin_id])) { + $output['send'][$plugin_id] = $this->tfaPluginSetupFormOverview($plugin, $user, TRUE); + } + } + } + } + + if (!empty($user_tfa)) { + if ($enabled && !empty($user_tfa['data']['plugins'])) { + $disable_url = Url::fromRoute('tfa.disable', ['user' => $user->id()]); + if ($disable_url->access()) { + $status_text = $this->t('Status: TFA enabled, set + @time. Disable TFA', [ + '@time' => $this->dateFormatter->format($user_tfa['saved']), + ':url' => $disable_url->toString(), + ]); + } + else { + $status_text = $this->t('Status: Multi-factor authentication enabled'); + } + } + else { + $status_text = $this->t('Status: Multi-factor authentication disabled'); + } + $output['status'] = [ + '#type' => 'markup', + '#markup' => '

' . $status_text . '

', + ]; + } + + $output['validation_skip_status'] = [ + '#type' => 'markup', + '#markup' => '

' . $this->t('Authentication setup: @remaining logins remain before multi-factor authentication is required', [ + '@remaining' => $config->get('validation_skip') - $user_tfa['validation_skipped'], + ]) . '

', + ]; + } + else { + $output['disabled'] = [ + '#type' => 'markup', + '#markup' => 'Currently there are no enabled plugins.', + ]; + } + + if ($this->canPerformReset($user)) { + $output['actions'] = ['#type' => 'actions']; + $output['actions']['reset_skip_attempts'] = [ + '#type' => 'submit', + '#value' => $this->t('Reset skip validation attempts'), + '#submit' => ['::resetSkipValidationAttempts'], + ]; + $output['account'] = [ + '#type' => 'value', + '#value' => $user, + ]; + } + + return $output; + } +} \ No newline at end of file diff --git a/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php b/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php index 6b9a8f3be..e32372130 100644 --- a/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php +++ b/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php @@ -77,7 +77,7 @@ public function getOverview(array $params) { '#access' => !$params['enabled'], '#links' => [ 'admin' => [ - 'title' => $this->t('Enable two-factor authentication via email'), + 'title' => $this->t('Enable multi-factor authentication via email'), 'url' => Url::fromRoute('tfa.validation.setup', [ 'user' => $params['account']->id(), 'method' => $params['plugin_id'], diff --git a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php index fbf1d9760..a4e4ec105 100644 --- a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php +++ b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php @@ -25,6 +25,11 @@ protected function alterRoutes(RouteCollection $collection) { if ($route = $collection->get('user.reset.login')) { $route->setDefault('_controller', '\Drupal\tide_tfa\Controller\TideTfaUserController::doResetPassLogin'); } + // TFA overview page (User → Security → TFA). + if ($route = $collection->get('tfa.overview')) { + $route->setDefault('_title', 'Multi-factor authentication'); + $route->setDefault('_form', '\Drupal\tide_tfa\Form\TideTfaOverviewForm'); + } } } diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index f9771b950..74cd0f4b4 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -24,3 +24,18 @@ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { } } } + +/** + * Implements hook_menu_local_tasks_alter(). + */ +function tide_tfa_menu_local_tasks_alter(&$data, $route_name, &$ref_root) { + // Check if the tfa.overview tab exists in the current render array + if (isset($data['tabs'][0]['tfa.overview'])) { + $data['tabs'][0]['tfa.overview']['#link']['title'] = t('Multi-factor authentication'); + } + + // Check if the tfa.settings tab exists + if (isset($data['tabs'][0]['tfa.settings'])) { + $data['tabs'][0]['tfa.settings']['#link']['title'] = t('Multi-factor authentication settings'); + } +} From bd28573d22c89de43a2c971851af205cf2358d1a Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Thu, 29 Jan 2026 15:40:50 +1100 Subject: [PATCH 2/9] [SD-1482] Updated tfa and tfa email otp module. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 6f35dfada..43f2ade60 100644 --- a/composer.json +++ b/composer.json @@ -59,8 +59,8 @@ "drupal/smtp": "^1.2", "drupal/stage_file_proxy": "^2.0", "drupal/tablefield": "2.4", - "drupal/tfa": "1.9", - "drupal/tfa_email_otp": "^1.0@beta", + "drupal/tfa": "^1.12", + "drupal/tfa_email_otp": "^1.0", "drupal/token_conditions": "dev-compatible-with-d10", "drupal/token_filter": "^2.0", "drupal/twig_field_value": "^2.0", From ba29307226eb9235bea12daf0a56e429c61ae103 Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Sun, 1 Feb 2026 21:55:29 +1100 Subject: [PATCH 3/9] [SD-1482] Added changes for tfa setup form and enabaled view password module. --- composer.json | 3 ++- .../src/Routing/TideTfaRouteSubscriber.php | 4 ++++ modules/tide_tfa/src/TideTfaOperation.php | 21 +++++++++++++++++++ modules/tide_tfa/tide_tfa.install | 11 ++++++++++ modules/tide_tfa/tide_tfa.module | 14 +++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 43f2ade60..0c3830885 100644 --- a/composer.json +++ b/composer.json @@ -124,7 +124,8 @@ "drupal/shield": "^1.8", "drupal/media_alias_display": "^2.1", "drupal/search_api_exclude_entity": "^3.0", - "drupal/default_paragraphs": "^2.0" + "drupal/default_paragraphs": "^2.0", + "drupal/view_password": "^6.0" }, "repositories": { "drupal": { diff --git a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php index a4e4ec105..28ffbe633 100644 --- a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php +++ b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php @@ -30,6 +30,10 @@ protected function alterRoutes(RouteCollection $collection) { $route->setDefault('_title', 'Multi-factor authentication'); $route->setDefault('_form', '\Drupal\tide_tfa\Form\TideTfaOverviewForm'); } + // TFA setup page. + if ($route = $collection->get('tfa.validation.setup')) { + $route->setDefault('_title', 'Multi-factor authentication setup'); + } } } diff --git a/modules/tide_tfa/src/TideTfaOperation.php b/modules/tide_tfa/src/TideTfaOperation.php index d9b329b6a..0f85f5862 100644 --- a/modules/tide_tfa/src/TideTfaOperation.php +++ b/modules/tide_tfa/src/TideTfaOperation.php @@ -145,5 +145,26 @@ public static function setupTfaRolePermissions() { user_role_grant_permissions($rid, $permissions); } } + + /** + * Setup view password. + */ + public static function setupViewPassword() { + // Enable view_password module if not already enabled. + $module_installer = \Drupal::service('module_installer'); + if (!$module_installer->isInstalled('view_password')) { + $module_installer->install(['view_password']); + } + + // Set view_password configuration. + $config = \Drupal::configFactory()->getEditable('view_password.settings'); + + $form_ids = $config->get('form_ids') ?? []; + + if (!in_array('tfa_setup', $form_ids, TRUE)) { + $form_ids[] = 'tfa_setup'; + $config->set('form_ids', $form_ids)->save(); + } + } } diff --git a/modules/tide_tfa/tide_tfa.install b/modules/tide_tfa/tide_tfa.install index 982a8ace0..9f3411a84 100644 --- a/modules/tide_tfa/tide_tfa.install +++ b/modules/tide_tfa/tide_tfa.install @@ -21,4 +21,15 @@ function tide_tfa_install() { // Setup TFA role permissions. $tideTfaOperation->setupTfaRolePermissions(); + + // Setup view password. + $tideTfaOperation->setupViewPassword(); +} + +/** + * Setup view password. + */ +function tide_tfa_update_10000() { + $tideTfaOperation = new TideTfaOperation(); + $tideTfaOperation->setupViewPassword(); } diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index 74cd0f4b4..c517af7b4 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -11,6 +11,7 @@ use Drupal\Core\Form\FormStateInterface; * Implements hook_form_alter(). */ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { + $current_user = \Drupal::currentUser(); // [SD-375] Bypass tfa during reset pass for all users. if ($form_id == 'tfa_settings_form') { if (isset($form['reset_pass_skip_enabled'])) { @@ -23,6 +24,19 @@ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { $form['actions']['send']['#value'] = t('Email me a verification code'); } } + if ($form_id == 'tfa_setup') { + $route_user = \Drupal::routeMatch()->getParameter('user'); + // If this is NOT "admin altering another user's TFA", + // change only the default description. + if ( + $current_user->id() === $route_user->id() + && !$current_user->hasPermission('administer tfa for other users') + ) { + $form['current_pass']['#description'] = t( + 'The current password is mandatory.' + ); + } + } } /** From 8c5cfdcebe9cdc0a99c02b323ef327bc9c728832 Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Sun, 1 Feb 2026 22:19:38 +1100 Subject: [PATCH 4/9] [SD-1482] Added Email setup from changes. --- modules/tide_tfa/src/Form/TideTfaOverviewForm.php | 1 + .../tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php index fd11fc8b5..bdb3a9b90 100644 --- a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php +++ b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php @@ -4,6 +4,7 @@ use Drupal\tfa\Form\TfaOverviewForm; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\user\UserInterface; class TideTfaOverviewForm extends TfaOverviewForm { diff --git a/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php b/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php index e32372130..e0fa9b1b0 100644 --- a/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php +++ b/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php @@ -28,10 +28,16 @@ public function getSetupForm(array $form, FormStateInterface $form_state) { $params = $form_state->getValues(); $userData = $this->userData->get('tfa', $params['account']->id(), 'tfa_email_otp'); + $form['email_otp_heading'] = [ + '#type' => 'html_tag', + '#tag' => 'h2', + '#value' => $this->t('Email authentication for login'), + ]; + // [SD-294] Changing the title and description. $form['enabled'] = [ '#type' => 'checkbox', - '#title' => $this->t('Yes, email me a verification code every time I log in'), + '#title' => $this->t('I agree to be sent a verification code via email each time I log in.'), '#description' => $this->t('Each single-use verification code expires after use, or after 10 minutes if not used.'), '#required' => TRUE, '#default_value' => $userData['enable'] ?? 0, From 4b02a1eb1189a9270bec6766490c4d367e5c7b23 Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Mon, 2 Feb 2026 16:30:33 +1100 Subject: [PATCH 5/9] [SD-1482] Added breadcrumb changes and some label, title and description updates. --- modules/tide_dashboard/tide_dashboard.module | 29 ++++++++++++++++++ .../tide_tfa/src/Form/TideTfaOverviewForm.php | 30 +++++++++++-------- .../Plugin/TfaSetup/TideTfaEmailOtpSetup.php | 2 +- modules/tide_tfa/tide_tfa.module | 6 ++++ tide_core.module | 10 +++++++ 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/modules/tide_dashboard/tide_dashboard.module b/modules/tide_dashboard/tide_dashboard.module index 56d5d3866..debe0a6dd 100644 --- a/modules/tide_dashboard/tide_dashboard.module +++ b/modules/tide_dashboard/tide_dashboard.module @@ -5,6 +5,11 @@ * Tide Dashboard. */ +use Drupal\Core\Link; +use Drupal\Core\Url; +use Drupal\Core\Breadcrumb\Breadcrumb; +use Drupal\Core\Routing\RouteMatchInterface; + /** * Implements hook_toolbar_alter(). * @@ -39,3 +44,27 @@ function tide_dashboard_user_login() { $request->query->set('destination', '/admin/workbench'); } } + +/** + * Implements hook_system_breadcrumb_alter(). + */ +function tide_dashboard_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) { + $links = $breadcrumb->getLinks(); + + if (!empty($links)) { + // Check if the first link is the Home link. + if ($links[0]->getUrl()->isRouted() && $links[0]->getUrl()->getRouteName() === '') { + + $new_url = Url::fromUserInput('/admin/workbench'); + $new_link = Link::fromTextAndUrl($links[0]->getText(), $new_url); + + // Swap the first link to workbench. + $links[0] = $new_link; + + $reflection = new \ReflectionClass($breadcrumb); + $property = $reflection->getProperty('links'); + $property->setAccessible(true); + $property->setValue($breadcrumb, $links); + } + } +} diff --git a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php index bdb3a9b90..66fa04a10 100644 --- a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php +++ b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php @@ -16,7 +16,7 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter $output['info'] = [ '#type' => 'markup', '#markup' => '

' . $this->t('Multi-factor authentication provides - additional security for your account. With multi-factor authentication enabled, + additional security for your account.
With multi-factor authentication enabled, you log in to the CMS with a verification code in addition to your username and password.') . '

', ]; @@ -79,18 +79,16 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter } if (!empty($user_tfa)) { + $status_text = ''; if ($enabled && !empty($user_tfa['data']['plugins'])) { $disable_url = Url::fromRoute('tfa.disable', ['user' => $user->id()]); if ($disable_url->access()) { - $status_text = $this->t('Status: TFA enabled, set - @time. Disable TFA', [ + $status_text = $this->t('Status: Multi-factor authentication enabled, set + @time. Disable Multi-factor authentication', [ '@time' => $this->dateFormatter->format($user_tfa['saved']), ':url' => $disable_url->toString(), ]); } - else { - $status_text = $this->t('Status: Multi-factor authentication enabled'); - } } else { $status_text = $this->t('Status: Multi-factor authentication disabled'); @@ -100,13 +98,19 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter '#markup' => '

' . $status_text . '

', ]; } + else { + $validation_skipped = $user_tfa['validation_skipped'] ?? 0; - $output['validation_skip_status'] = [ - '#type' => 'markup', - '#markup' => '

' . $this->t('Authentication setup: @remaining logins remain before multi-factor authentication is required', [ - '@remaining' => $config->get('validation_skip') - $user_tfa['validation_skipped'], - ]) . '

', - ]; + $output['validation_skip_status'] = [ + '#type' => 'markup', + '#markup' => '

' . $this->t( + 'Authentication setup: @remaining logins remain before multi-factor authentication is required', + [ + '@remaining' => $config->get('validation_skip') - $validation_skipped, + ] + ) . '

', + ]; + } } else { $output['disabled'] = [ @@ -130,4 +134,4 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter return $output; } -} \ No newline at end of file +} diff --git a/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php b/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php index e0fa9b1b0..bb5c98720 100644 --- a/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php +++ b/modules/tide_tfa/src/Plugin/TfaSetup/TideTfaEmailOtpSetup.php @@ -64,7 +64,7 @@ public function getOverview(array $params) { // [SD-294] Modify the description. $description = ''; if ($params['enabled']) { - $description .= $this->t('

Enabled

'); + $description .= $this->t('

Multi-factor authentication enabled

'); } $output = [ 'heading' => [ diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index c517af7b4..682ee574f 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -37,6 +37,12 @@ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { ); } } + if ($form_id == 'tfa_entry_form') { + if (isset($form['code'])) { + $form['code']['#title'] = t('Verification code'); + $form['code']['#description'] = t('The verification code field is mandatory.'); + } + } } /** diff --git a/tide_core.module b/tide_core.module index 5b3c92b6d..6405000ea 100644 --- a/tide_core.module +++ b/tide_core.module @@ -899,3 +899,13 @@ function tide_core_field_widget_complete_form_alter(&$field_widget_complete_form $field_widget_complete_form['widget'][0]['state']['#default_value'] = 'draft'; } } + +/** + * Implements hook_menu_local_tasks_alter(). + */ +function tide_core_menu_local_tasks_alter(&$data, $route_name, &$ref_root) { + // Update "View" to "View profile" + if (isset($data['tabs'][0]['entity.user.canonical'])) { + $data['tabs'][0]['entity.user.canonical']['#link']['title'] = t('View profile'); + } +} From fdda4c95f937a8b6d12fe96bbace454f2ea0c44e Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Tue, 3 Feb 2026 15:58:22 +1100 Subject: [PATCH 6/9] [SD-1482] Updated dashboard module to redirect to tfa setup form after user login and lint fix. --- modules/tide_dashboard/tide_dashboard.module | 49 ++++++++++++++----- .../tide_tfa/src/Form/TideTfaOverviewForm.php | 10 +++- modules/tide_tfa/src/TideTfaOperation.php | 9 ++-- modules/tide_tfa/tide_tfa.module | 4 +- tide_core.module | 2 +- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/modules/tide_dashboard/tide_dashboard.module b/modules/tide_dashboard/tide_dashboard.module index debe0a6dd..b5ec69ea4 100644 --- a/modules/tide_dashboard/tide_dashboard.module +++ b/modules/tide_dashboard/tide_dashboard.module @@ -5,10 +5,11 @@ * Tide Dashboard. */ -use Drupal\Core\Link; -use Drupal\Core\Url; use Drupal\Core\Breadcrumb\Breadcrumb; +use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Url; +use Drupal\user\UserInterface; /** * Implements hook_toolbar_alter(). @@ -35,12 +36,38 @@ function _tide_dashboard_workbench_content_title_callback() { return t('Dashboard'); } -/** - * Implements hook_user_login(). - */ -function tide_dashboard_user_login() { +function tide_dashboard_user_login(UserInterface $account) { $request = \Drupal::service('request_stack')->getCurrentRequest(); - if (($request->query->has('destination')) === FALSE) { + $uid = $account->id(); + + $module_handler = \Drupal::service('module_handler'); + if ($module_handler->moduleExists('tfa')) { + // Check which roles are forced to use tfa. + $tfa_config = \Drupal::config('tfa.settings'); + $required_roles = $tfa_config->get('required_roles') ?: []; + + // Check if the current user has any of the required roles. + $user_roles = $account->getRoles(); + $is_required = (bool) array_intersect($required_roles, $user_roles); + + // If they ARE required to have it, check if they've actually set it up. + if ($is_required) { + $user_data = \Drupal::service('user.data'); + $tfa_settings = $user_data->get('tfa', $uid, 'tfa_user_settings'); + // check 'status' AND ensure the 'plugins' array is not empty. + $has_active_plugins = !empty($tfa_settings['data']['plugins']); + $is_enabled = !empty($tfa_settings['status']) && $tfa_settings['status'] == 1; + + // If user don't have active plugins, they haven't finished setup. + if (!$is_enabled || !$has_active_plugins) { + $request->query->set('destination', "/user/$uid/security/tfa"); + return; + } + } + } + + // Fallback: If not required or already setup, go to workbench. + if (!$request->query->has('destination')) { $request->query->set('destination', '/admin/workbench'); } } @@ -54,16 +81,16 @@ function tide_dashboard_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMa if (!empty($links)) { // Check if the first link is the Home link. if ($links[0]->getUrl()->isRouted() && $links[0]->getUrl()->getRouteName() === '') { - + $new_url = Url::fromUserInput('/admin/workbench'); $new_link = Link::fromTextAndUrl($links[0]->getText(), $new_url); - + // Swap the first link to workbench. $links[0] = $new_link; - + $reflection = new \ReflectionClass($breadcrumb); $property = $reflection->getProperty('links'); - $property->setAccessible(true); + $property->setAccessible(TRUE); $property->setValue($breadcrumb, $links); } } diff --git a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php index 66fa04a10..855e0792e 100644 --- a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php +++ b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php @@ -2,17 +2,24 @@ namespace Drupal\tide_tfa\Form; -use Drupal\tfa\Form\TfaOverviewForm; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\tfa\Form\TfaOverviewForm; use Drupal\user\UserInterface; +/** + * Provides a customised TFA overview form for Tide. + * + * Extends the core TFA overview form to alter the presentation and behaviour + * of multi-factor authentication setup and status within the Tide CMS. + */ class TideTfaOverviewForm extends TfaOverviewForm { /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, UserInterface $user = NULL) { + $output = []; $output['info'] = [ '#type' => 'markup', '#markup' => '

' . $this->t('Multi-factor authentication provides @@ -134,4 +141,5 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter return $output; } + } diff --git a/modules/tide_tfa/src/TideTfaOperation.php b/modules/tide_tfa/src/TideTfaOperation.php index 0f85f5862..7ba22b0b5 100644 --- a/modules/tide_tfa/src/TideTfaOperation.php +++ b/modules/tide_tfa/src/TideTfaOperation.php @@ -145,15 +145,16 @@ public static function setupTfaRolePermissions() { user_role_grant_permissions($rid, $permissions); } } - + /** * Setup view password. */ public static function setupViewPassword() { // Enable view_password module if not already enabled. - $module_installer = \Drupal::service('module_installer'); - if (!$module_installer->isInstalled('view_password')) { - $module_installer->install(['view_password']); + $moduleHandler = \Drupal::service('module_handler'); + $moduleInstaller = \Drupal::service('module_installer'); + if (!$moduleHandler->moduleExists('view_password')) { + $moduleInstaller->install(['view_password']); } // Set view_password configuration. diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index 682ee574f..4933f8c13 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -49,12 +49,12 @@ function tide_tfa_form_alter(&$form, FormStateInterface $form_state, $form_id) { * Implements hook_menu_local_tasks_alter(). */ function tide_tfa_menu_local_tasks_alter(&$data, $route_name, &$ref_root) { - // Check if the tfa.overview tab exists in the current render array + // Check if the tfa.overview tab exists. if (isset($data['tabs'][0]['tfa.overview'])) { $data['tabs'][0]['tfa.overview']['#link']['title'] = t('Multi-factor authentication'); } - // Check if the tfa.settings tab exists + // Check if the tfa.settings tab exists. if (isset($data['tabs'][0]['tfa.settings'])) { $data['tabs'][0]['tfa.settings']['#link']['title'] = t('Multi-factor authentication settings'); } diff --git a/tide_core.module b/tide_core.module index 6405000ea..834311f77 100644 --- a/tide_core.module +++ b/tide_core.module @@ -904,7 +904,7 @@ function tide_core_field_widget_complete_form_alter(&$field_widget_complete_form * Implements hook_menu_local_tasks_alter(). */ function tide_core_menu_local_tasks_alter(&$data, $route_name, &$ref_root) { - // Update "View" to "View profile" + // Update "View" to "View profile". if (isset($data['tabs'][0]['entity.user.canonical'])) { $data['tabs'][0]['entity.user.canonical']['#link']['title'] = t('View profile'); } From bcda8e00516eb44e16af7a37123b204fd117b1e5 Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Wed, 4 Feb 2026 12:22:40 +1100 Subject: [PATCH 7/9] [SD-1482] Fixed type miss match error during build and lint fix. --- modules/tide_dashboard/tide_dashboard.module | 7 +++++-- modules/tide_tfa/src/TideTfaOperation.php | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/tide_dashboard/tide_dashboard.module b/modules/tide_dashboard/tide_dashboard.module index b5ec69ea4..23e48b6de 100644 --- a/modules/tide_dashboard/tide_dashboard.module +++ b/modules/tide_dashboard/tide_dashboard.module @@ -36,6 +36,9 @@ function _tide_dashboard_workbench_content_title_callback() { return t('Dashboard'); } +/** + * Implements hook_user_login(). + */ function tide_dashboard_user_login(UserInterface $account) { $request = \Drupal::service('request_stack')->getCurrentRequest(); $uid = $account->id(); @@ -45,7 +48,7 @@ function tide_dashboard_user_login(UserInterface $account) { // Check which roles are forced to use tfa. $tfa_config = \Drupal::config('tfa.settings'); $required_roles = $tfa_config->get('required_roles') ?: []; - + // Check if the current user has any of the required roles. $user_roles = $account->getRoles(); $is_required = (bool) array_intersect($required_roles, $user_roles); @@ -54,7 +57,7 @@ function tide_dashboard_user_login(UserInterface $account) { if ($is_required) { $user_data = \Drupal::service('user.data'); $tfa_settings = $user_data->get('tfa', $uid, 'tfa_user_settings'); - // check 'status' AND ensure the 'plugins' array is not empty. + // Check 'status' AND ensure the 'plugins' array is not empty. $has_active_plugins = !empty($tfa_settings['data']['plugins']); $is_enabled = !empty($tfa_settings['status']) && $tfa_settings['status'] == 1; diff --git a/modules/tide_tfa/src/TideTfaOperation.php b/modules/tide_tfa/src/TideTfaOperation.php index 7ba22b0b5..56777f530 100644 --- a/modules/tide_tfa/src/TideTfaOperation.php +++ b/modules/tide_tfa/src/TideTfaOperation.php @@ -160,7 +160,10 @@ public static function setupViewPassword() { // Set view_password configuration. $config = \Drupal::configFactory()->getEditable('view_password.settings'); - $form_ids = $config->get('form_ids') ?? []; + $form_ids = $config->get('form_ids'); + $form_ids = is_array($form_ids) ? $form_ids : (array) $form_ids; + + $form_ids = array_filter($form_ids); if (!in_array('tfa_setup', $form_ids, TRUE)) { $form_ids[] = 'tfa_setup'; From 4eb2a59f2a8e141682c2b4c09a74909e1c8d2b9a Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Wed, 4 Feb 2026 12:46:50 +1100 Subject: [PATCH 8/9] [SD-1482] Fixed type miss match error during build for view password settings. --- modules/tide_tfa/src/TideTfaOperation.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/tide_tfa/src/TideTfaOperation.php b/modules/tide_tfa/src/TideTfaOperation.php index 56777f530..db380fd5e 100644 --- a/modules/tide_tfa/src/TideTfaOperation.php +++ b/modules/tide_tfa/src/TideTfaOperation.php @@ -159,15 +159,15 @@ public static function setupViewPassword() { // Set view_password configuration. $config = \Drupal::configFactory()->getEditable('view_password.settings'); - - $form_ids = $config->get('form_ids'); - $form_ids = is_array($form_ids) ? $form_ids : (array) $form_ids; - - $form_ids = array_filter($form_ids); + $form_ids_string = $config->get('form_ids') ?? ''; + $form_ids = array_filter(array_map('trim', explode(',', $form_ids_string))); if (!in_array('tfa_setup', $form_ids, TRUE)) { $form_ids[] = 'tfa_setup'; - $config->set('form_ids', $form_ids)->save(); + // Convert back to a comma-separated string. + // view_password only accept string for the schema. + $new_value = implode(',', $form_ids); + $config->set('form_ids', $new_value)->save(); } } From 03880285feb7eb51c6cebcffeb47d47ef6b0ca3f Mon Sep 17 00:00:00 2001 From: Md Nadim Hossain Date: Thu, 5 Feb 2026 12:46:54 +1100 Subject: [PATCH 9/9] [SD-1482] Added custom messages and replaced tfa module messages. --- .../tide_tfa/src/Form/TideTfaOverviewForm.php | 8 +++- .../src/Routing/TideTfaRouteSubscriber.php | 15 ++++++ modules/tide_tfa/tide_tfa.module | 46 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php index 855e0792e..0a1cfe099 100644 --- a/modules/tide_tfa/src/Form/TideTfaOverviewForm.php +++ b/modules/tide_tfa/src/Form/TideTfaOverviewForm.php @@ -85,8 +85,8 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter } } + // Moved it inside to show the status if only TFA is enabled. if (!empty($user_tfa)) { - $status_text = ''; if ($enabled && !empty($user_tfa['data']['plugins'])) { $disable_url = Url::fromRoute('tfa.disable', ['user' => $user->id()]); if ($disable_url->access()) { @@ -96,6 +96,9 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter ':url' => $disable_url->toString(), ]); } + else { + $status_text = $this->t('Status: Multi-factor authentication enabled'); + } } else { $status_text = $this->t('Status: Multi-factor authentication disabled'); @@ -105,7 +108,8 @@ public function buildForm(array $form, FormStateInterface $form_state, UserInter '#markup' => '

' . $status_text . '

', ]; } - else { + + if (!$config->get('forced')) { $validation_skipped = $user_tfa['validation_skipped'] ?? 0; $output['validation_skip_status'] = [ diff --git a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php index 28ffbe633..e6a1d14ed 100644 --- a/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php +++ b/modules/tide_tfa/src/Routing/TideTfaRouteSubscriber.php @@ -4,6 +4,7 @@ use Drupal\Core\Routing\RouteSubscriberBase; use Symfony\Component\Routing\RouteCollection; +use Drupal\Core\Routing\RoutingEvents; /** * Listens to the dynamic route events. @@ -34,6 +35,20 @@ protected function alterRoutes(RouteCollection $collection) { if ($route = $collection->get('tfa.validation.setup')) { $route->setDefault('_title', 'Multi-factor authentication setup'); } + // TFA disable page. + if ($route = $collection->get('tfa.disable')) { + $route->setDefault('_title', 'Disable multi-factor authentication'); + } + } + + /** + * Attempt to be the last subscriber to allow our routes to take priority. + */ + public static function getSubscribedEvents(): array { + $events = parent::getSubscribedEvents(); + // Use lower priority than tfa module. + $events[RoutingEvents::ALTER] = ['onAlterRoutes', (PHP_INT_MIN - 1)]; + return $events; } } diff --git a/modules/tide_tfa/tide_tfa.module b/modules/tide_tfa/tide_tfa.module index 4933f8c13..4c9bb0d12 100644 --- a/modules/tide_tfa/tide_tfa.module +++ b/modules/tide_tfa/tide_tfa.module @@ -59,3 +59,49 @@ function tide_tfa_menu_local_tasks_alter(&$data, $route_name, &$ref_root) { $data['tabs'][0]['tfa.settings']['#link']['title'] = t('Multi-factor authentication settings'); } } + +/** + * Implements hook_preprocess_HOOK(). + */ +function tide_tfa_preprocess_status_messages(&$variables) { + if (!$variables && !isset($variables['message_list'])) { + return; + } + + // Custom messages to replace TFA messages. + if (isset($variables['message_list']['error'])) { + foreach ($variables['message_list']['error'] as $key => $message) { + $message_string = (string) $message; + + // Convert the error message into warning message. + if (strpos($message_string, 'You are required to') !== false && + strpos($message_string, 'setup two-factor authentication') !== false && + strpos($message_string, 'unable to login') !== false) { + + if (!isset($variables['message_list']['warning'])) { + $variables['message_list']['warning'] = []; + } + + $variables['message_list']['warning'][] = t('You are required to set up multi-factor authentication. Select the link below to enable multi-factor authentication via email.'); + + // Remove the original message from the error list. + unset($variables['message_list']['error'][$key]); + } + } + + // Clean up: If the error list is now empty, remove the error key entirely. + if (empty($variables['message_list']['error'])) { + unset($variables['message_list']['error']); + } + } + if (isset($variables['message_list']['status'])) { + foreach ($variables['message_list']['status'] as $key => $message) { + if (strpos((string) $message, 'TFA setup complete.') !== FALSE) { + $variables['message_list']['status'][$key] = t("Multi-factor authentication setup is complete. Go to the dashboard."); + } + if (strpos((string) $message, 'TFA has been disabled.') !== FALSE) { + $variables['message_list']['status'][$key] = t("Multi-factor authentication has been disabled. Go to the dashboard."); + } + } + } +}