From 8656ecf00a1afa35ad3b4695e7c7e41d63ebc5f9 Mon Sep 17 00:00:00 2001 From: Johannes Rauh Date: Thu, 11 Dec 2025 11:40:58 +0100 Subject: [PATCH] Add CLI commands for 2FA The following commands are added: - `icingacli twofactor list` lists all users that have 2FA enabled. - `icingacli twofactor disable []` disables 2FA for a user. --- application/clicommands/TwofactorCommand.php | 116 +++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 application/clicommands/TwofactorCommand.php diff --git a/application/clicommands/TwofactorCommand.php b/application/clicommands/TwofactorCommand.php new file mode 100644 index 000000000..3dc754e60 --- /dev/null +++ b/application/clicommands/TwofactorCommand.php @@ -0,0 +1,116 @@ +getDb()->select( + (new Select()) + ->from('icingaweb_2fa') + ->columns(['username', 'ctime']) + )->fetchAll(); + + if (empty($rows)) { + echo "Currently there are no users that have 2FA enabled.\n"; + + return; + } + + printf("%-20s %-20s\n", 'USER', 'ENABLED 2FA'); + + foreach ($rows as $row) { + printf( + "%-20s %-20s\n", + $row->username, + DateTime::createFromFormat('U.u', $row->ctime / 1000)->format('Y-m-d H:i:s') + ); + } + + echo "\n"; + } + + /** + * Disable 2FA for a specific user + * + * This command disables 2FA for a specific user. It asks for confirmation before deleting the secret. The deletion + * cannot be undone. + * + * USAGE + * + * icingacli twofactor disable [] + * + * OPTIONS + * + * --force Disable 2FA for user without confirmation + */ + public function disableAction(): void + { + $user = $this->params->shift(); + + if (! $user) { + fwrite(STDERR, "User must be provided!\n"); + $this->showUsage('disable'); + + exit(1); + } + + if (! $this->params->shift('force')) { + $input = readline(sprintf( + "Are you sure you want to disable 2FA for user '%s'? This cannot be undone! [y/N] ", + $user + )); + + if (! $input || ! in_array(strtolower(trim($input)), ['y', 'yes'])) { + echo "No changes made.\n"; + + return; + } + } + + try { + $delete = $this->getDb()->prepexec( + (new Delete()) + ->from('icingaweb_2fa') + ->where(['LOWER(username) = ?' => strtolower($user)]) + ); + } catch (Throwable $e) { + fprintf( + STDERR, + "%s: Failed to disable 2FA for '%s': %s\n", + $this->screen->colorize('ERROR', 'red'), + $user, + $e->getMessage() + ); + + exit(1); + } + + if ($delete->rowCount() < 1) { + printf("The user '%s' doesn't have 2FA enabled.\n", $user); + + return; + } + + printf("Successfully disabled 2FA for user '%s'.\n", $user); + } +}