Skip to content

Commit 2dae780

Browse files
committed
implement 2fa remember browser, fixes #1259
Signed-off-by: Michael Kaufmann <[email protected]>
1 parent bda24d7 commit 2dae780

File tree

9 files changed

+98
-11
lines changed

9 files changed

+98
-11
lines changed

index.php

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
exit();
7373
}
7474
$code = Request::post('2fa_code');
75+
$remember = Request::post('2fa_remember');
7576
// verify entered code
7677
$tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname'));
7778
// get user-data
@@ -105,20 +106,49 @@
105106
$userinfo['adminsession'] = $isadmin;
106107
$userinfo['userid'] = $uid;
107108

108-
// if not successful somehow - start again
109-
if (!finishLogin($userinfo)) {
110-
Response::redirectTo('index.php', [
111-
'showmessage' => '2'
112-
]);
113-
}
114-
115109
// when using email-2fa, remove the one-time-code
116110
if ($userinfo['type_2fa'] == '1') {
117111
$del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid");
118112
$userinfo = Database::pexecute_first($del_stmt, [
119113
'uid' => $uid
120114
]);
121115
}
116+
117+
// when remember is activated, set the cookie
118+
if ($remember) {
119+
$selector = base64_encode(Froxlor::genSessionId(9));
120+
$authenticator = Froxlor::genSessionId(33);
121+
$valid_until = time()+60*60*24*30;
122+
$ins_stmt = Database::prepare("
123+
INSERT INTO `".TABLE_PANEL_2FA_TOKENS."` SET
124+
`selector` = :selector,
125+
`token` = :authenticator,
126+
`userid` = :userid,
127+
`valid_until` = :valid_until
128+
");
129+
Database::pexecute($ins_stmt, [
130+
'selector' => $selector,
131+
'authenticator' => hash('sha256', $authenticator),
132+
'userid' => $uid,
133+
'valid_until' => $valid_until
134+
]);
135+
$cookie_params = [
136+
'expires' => $valid_until, // 30 days
137+
'path' => '/',
138+
'domain' => UI::getCookieHost(),
139+
'secure' => UI::requestIsHttps(),
140+
'httponly' => true,
141+
'samesite' => 'Strict'
142+
];
143+
setcookie('frx_2fa_remember', $selector.':'.base64_encode($authenticator), $cookie_params);
144+
}
145+
146+
// if not successful somehow - start again
147+
if (!finishLogin($userinfo)) {
148+
Response::redirectTo('index.php', [
149+
'showmessage' => '2'
150+
]);
151+
}
122152
exit();
123153
}
124154
// wrong 2fa code - treat like "wrong password"
@@ -349,6 +379,22 @@
349379

350380
// 2FA activated
351381
if (Settings::Get('2fa.enabled') == '1' && $userinfo['type_2fa'] > 0) {
382+
383+
// check for remember cookie
384+
if (!empty($_COOKIE['frx_2fa_remember'])) {
385+
list($selector, $authenticator) = explode(':', $_COOKIE['frx_2fa_remember']);
386+
$sel_stmt = Database::prepare("SELECT `token` FROM `".TABLE_PANEL_2FA_TOKENS."` WHERE `selector` = :selector AND `userid` = :uid AND `valid_until` >= UNIX_TIMESTAMP()");
387+
$token_check = Database::pexecute_first($sel_stmt, ['selector' => $selector, 'uid' => $userinfo[$uid]]);
388+
if ($token_check && hash_equals($token_check['token'], hash('sha256', base64_decode($authenticator)))) {
389+
if (!finishLogin($userinfo)) {
390+
Response::redirectTo('index.php', [
391+
'showmessage' => '2'
392+
]);
393+
}
394+
exit();
395+
}
396+
}
397+
352398
// redirect to code-enter-page
353399
$_SESSION['secret_2fa'] = ($userinfo['type_2fa'] == 2 ? $userinfo['data_2fa'] : 'email');
354400
$_SESSION['uid_2fa'] = $userinfo[$uid];
@@ -829,8 +875,8 @@ function finishLogin($userinfo)
829875
$theme = $userinfo['theme'];
830876
} else {
831877
$theme = Settings::Get('panel.default_theme');
832-
CurrentUser::setField('theme', $theme);
833878
}
879+
CurrentUser::setField('theme', $theme);
834880

835881
$qryparams = [];
836882
if (!empty($_SESSION['lastqrystr'])) {

install/froxlor.sql.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@
731731
('panel', 'settings_mode', '0'),
732732
('panel', 'menu_collapsed', '1'),
733733
('panel', 'version', '2.2.0-rc1'),
734-
('panel', 'db_version', '202401090');
734+
('panel', 'db_version', '202407200');
735735
736736
737737
DROP TABLE IF EXISTS `panel_tasks`;
@@ -1049,4 +1049,15 @@
10491049
`allowed_from` text NOT NULL,
10501050
UNIQUE KEY `loginname` (`loginname`)
10511051
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
1052+
1053+
1054+
DROP TABLE IF EXISTS `panel_2fa_tokens`;
1055+
CREATE TABLE `panel_2fa_tokens` (
1056+
`id` int(11) NOT NULL auto_increment,
1057+
`selector` varchar(20) NOT NULL,
1058+
`token` varchar(200) NOT NULL,
1059+
`userid` int(11) NOT NULL default '0',
1060+
`valid_until` int(15) NOT NULL,
1061+
PRIMARY KEY (id)
1062+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
10521063
FROXLORSQL;

install/updates/froxlor/update_2.2.inc.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,21 @@
122122
Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false);
123123
Froxlor::updateToVersion('2.2.0-rc1');
124124
}
125+
126+
if (Froxlor::isDatabaseVersion('202401090')) {
127+
128+
Update::showUpdateStep("Adding new table for 2fa tokens");
129+
Database::query("DROP TABLE IF EXISTS `panel_2fa_tokens`;");
130+
$sql = "CREATE TABLE `panel_2fa_tokens` (
131+
`id` int(11) NOT NULL auto_increment,
132+
`selector` varchar(20) NOT NULL,
133+
`token` varchar(200) NOT NULL,
134+
`userid` int(11) NOT NULL default '0',
135+
`valid_until` int(15) NOT NULL,
136+
PRIMARY KEY (id)
137+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
138+
Database::query($sql);
139+
Update::lastStepStatus(0);
140+
141+
Froxlor::updateToDbVersion('202407200');
142+
}

lib/Froxlor/Cli/MasterCron.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
171171
FroxlorLogger::getInstanceOf()->setCronLog(0);
172172
}
173173

174-
// clean up possible old login-links
174+
// clean up possible old login-links and 2fa tokens
175175
Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
176+
Database::query("DELETE FROM `" . TABLE_PANEL_2FA_TOKENS . "` WHERE `valid_until` < UNIX_TIMESTAMP()");
176177

177178
return $result;
178179
}

lib/Froxlor/Froxlor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ final class Froxlor
3434
const VERSION = '2.2.0-rc1';
3535

3636
// Database version (YYYYMMDDC where C is a daily counter)
37-
const DBVERSION = '202401090';
37+
const DBVERSION = '202407200';
3838

3939
// Distribution branding-tag (used for Debian etc.)
4040
const BRANDING = '';

lib/tables.inc.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@
5757
const TABLE_API_KEYS = 'api_keys';
5858
const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns';
5959
const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks';
60+
const TABLE_PANEL_2FA_TOKENS = 'panel_2fa_tokens';

lng/de.lng.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,7 @@
10321032
'combination_not_found' => 'Kombination aus Benutzername und E-Mail Adresse stimmen nicht überein.',
10331033
'2fa' => 'Zwei-Faktor Authentifizierung (2FA)',
10341034
'2facode' => 'Bitte 2FA Code angeben',
1035+
'2faremember' => 'Browser vertrauen',
10351036
],
10361037
'mails' => [
10371038
'pop_success' => [

lng/en.lng.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,7 @@
11041104
'combination_not_found' => 'Combination of user and email address not found.',
11051105
'2fa' => 'Two-factor authentication (2FA)',
11061106
'2facode' => 'Please enter 2FA code',
1107+
'2faremember' => 'Trust browser',
11071108
],
11081109
'mails' => [
11091110
'pop_success' => [

templates/Froxlor/login/enter2fa.html.twig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222
<input class="form-control" type="text" name="2fa_code" id="2fa_code" value="" autocomplete="off" autofocus required/>
2323
</div>
2424

25+
<div class="mb-3">
26+
<div class="form-check form-switch">
27+
<input type="hidden" name="2fa_remember" value="0"/>
28+
<input class="form-check-input" type="checkbox" id="2fa_remember" name="2fa_remember" value="1">
29+
<label class="form-check-label" for="2fa_remember">{{ lng('login.2faremember') }}</label>
30+
</div>
31+
</div>
32+
2533
</div>
2634

2735
<div class="card-body d-grid gap-2">

0 commit comments

Comments
 (0)