aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Inverle <inverle@proton.me> 2025-07-31 13:53:14 +0200
committerGravatar GitHub <noreply@github.com> 2025-07-31 13:53:14 +0200
commit3ce64d271b2b470bd6c9f7294946347dcdfed9b9 (patch)
tree56b5a0928f1ce2b0486e866abd5456bc43ea8ed9
parentd0425f8c3ab14e72142b1a4f946d57b408f26c88 (diff)
Implement sudo mode / reauthentication (#7753)
* Implement sudo mode / reauthentication * i18n: fr * generate flags * Improvements * Remove HMAC check * Don't require reauth to access logs when signed in as admin * Notify user of bad login via notification instead --------- Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
-rw-r--r--app/Controllers/authController.php33
-rw-r--r--app/Controllers/updateController.php4
-rw-r--r--app/Controllers/userController.php57
-rw-r--r--app/Models/Auth.php51
-rw-r--r--app/Models/SystemConfiguration.php2
-rw-r--r--app/i18n/cs/gen.php5
-rw-r--r--app/i18n/de/gen.php5
-rw-r--r--app/i18n/el/gen.php5
-rw-r--r--app/i18n/en-us/gen.php5
-rw-r--r--app/i18n/en/gen.php5
-rw-r--r--app/i18n/es/gen.php5
-rw-r--r--app/i18n/fa/gen.php5
-rw-r--r--app/i18n/fi/gen.php5
-rw-r--r--app/i18n/fr/gen.php5
-rw-r--r--app/i18n/he/gen.php5
-rwxr-xr-xapp/i18n/hu/gen.php5
-rw-r--r--app/i18n/id/gen.php5
-rw-r--r--app/i18n/it/gen.php5
-rw-r--r--app/i18n/ja/gen.php5
-rw-r--r--app/i18n/ko/gen.php5
-rw-r--r--app/i18n/lv/gen.php5
-rw-r--r--app/i18n/nl/gen.php5
-rw-r--r--app/i18n/oc/gen.php5
-rw-r--r--app/i18n/pl/gen.php5
-rw-r--r--app/i18n/pt-br/gen.php5
-rw-r--r--app/i18n/pt-pt/gen.php5
-rw-r--r--app/i18n/ru/gen.php5
-rw-r--r--app/i18n/sk/gen.php5
-rw-r--r--app/i18n/tr/gen.php5
-rw-r--r--app/i18n/zh-cn/gen.php5
-rw-r--r--app/i18n/zh-tw/gen.php5
-rw-r--r--app/views/auth/reauth.phtml32
-rw-r--r--config.default.php7
-rw-r--r--docs/i18n/flags/gen/de.svg2
-rw-r--r--docs/i18n/flags/gen/id.svg2
-rw-r--r--lib/core-extensions/UserJS/extension.php4
-rw-r--r--lib/core-extensions/UserJS/metadata.json2
-rw-r--r--p/scripts/extra.js5
38 files changed, 321 insertions, 10 deletions
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
index b090eb486..6b8d924d6 100644
--- a/app/Controllers/authController.php
+++ b/app/Controllers/authController.php
@@ -21,6 +21,10 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
Minz_Error::error(403);
}
+ if (FreshRSS_Auth::requestReauth()) {
+ return;
+ }
+
FreshRSS_View::prependTitle(_t('admin.auth.title') . ' · ');
if (Minz_Request::isPost()) {
@@ -219,6 +223,35 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
}
}
+ public function reauthAction(): void {
+ if (!FreshRSS_Auth::hasAccess()) {
+ Minz_Error::error(403);
+ return;
+ }
+ /** @var array{c?: string, a?: string, params?: array<string, mixed>} $redirect */
+ $redirect = Minz_Url::unserialize(Minz_Request::paramString('r'));
+ if (!FreshRSS_Auth::needsReauth()) {
+ Minz_Request::forward($redirect, true);
+ return;
+ }
+ if (Minz_Request::isPost()) {
+ $username = Minz_User::name() ?? '';
+ $nonce = Minz_Session::paramString('nonce');
+ $challenge = Minz_Request::paramString('challenge');
+ if (!FreshRSS_FormAuth::checkCredentials(
+ $username, FreshRSS_Context::userConf()->passwordHash, $nonce, $challenge
+ )) {
+ Minz_Request::setBadNotification(_t('feedback.auth.login.invalid'));
+ } else {
+ Minz_Session::_param('lastReauth', time());
+ Minz_Request::forward($redirect, true);
+ return;
+ }
+ }
+ FreshRSS_View::prependTitle(_t('gen.auth.reauth.title') . ' · ');
+ FreshRSS_View::appendScript(Minz_Url::display('/scripts/vendor/bcrypt.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/bcrypt.js')));
+ }
+
/**
* This action removes all accesses of the current user.
*/
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index f6ed00986..b76b528d8 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -270,6 +270,10 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
Minz_Request::forward(['c' => 'update'], true);
}
+ if (FreshRSS_Auth::requestReauth()) {
+ return;
+ }
+
if (Minz_Request::paramBoolean('post_conf')) {
if (self::isGit()) {
$res = !self::hasGitUpdate();
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index 05d73f971..c4c3c00a8 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -72,6 +72,10 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
}
if (Minz_Request::isPost()) {
+ if (self::reauthRedirect()) {
+ return;
+ }
+
$username = Minz_Request::paramString('username');
$newPasswordPlain = Minz_User::name() !== $username ? Minz_Request::paramString('newPasswordPlain', true) : '';
@@ -190,21 +194,41 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
}
}
+ public static function reauthRedirect(): bool {
+ $url_redirect = [
+ 'c' => 'user',
+ 'a' => 'manage',
+ 'params' => [],
+ ];
+ $username = Minz_Request::paramStringNull('username');
+ if ($username !== null) {
+ $url_redirect['a'] = 'details';
+ $url_redirect['params']['username'] = $username;
+ }
+ return FreshRSS_Auth::requestReauth($url_redirect);
+ }
+
public function purgeAction(): void {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
- if (Minz_Request::isPost()) {
- $username = Minz_Request::paramString('username');
+ if (!Minz_Request::isPost()) {
+ Minz_Error::error(403);
+ }
- if (!FreshRSS_UserDAO::exists($username)) {
- Minz_Error::error(404);
- }
+ if (self::reauthRedirect()) {
+ return;
+ }
+
+ $username = Minz_Request::paramString('username');
- $feedDAO = FreshRSS_Factory::createFeedDao($username);
- $feedDAO->purge();
+ if (!FreshRSS_UserDAO::exists($username)) {
+ Minz_Error::error(404);
}
+
+ $feedDAO = FreshRSS_Factory::createFeedDao($username);
+ $feedDAO->purge();
}
/**
@@ -215,6 +239,10 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
Minz_Error::error(403);
}
+ if (self::reauthRedirect()) {
+ return;
+ }
+
FreshRSS_View::prependTitle(_t('admin.user.title') . ' · ');
if (Minz_Request::isPost()) {
@@ -337,6 +365,10 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
Minz_Error::error(403);
}
+ if (self::reauthRedirect()) {
+ return;
+ }
+
if (Minz_Request::isPost()) {
$new_user_name = Minz_Request::paramString('new_user_name');
$email = Minz_Request::paramString('new_user_email');
@@ -602,7 +634,10 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
$username, FreshRSS_Context::userConf()->passwordHash,
$nonce, $challenge
);
+ } elseif (self::reauthRedirect()) {
+ return;
}
+
if ($ok) {
$ok &= self::deleteUser($username);
}
@@ -647,6 +682,10 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
Minz_Error::error(403);
}
+ if (self::reauthRedirect()) {
+ return;
+ }
+
$username = Minz_Request::paramString('username');
if (!FreshRSS_UserDAO::exists($username)) {
Minz_Error::error(404);
@@ -682,6 +721,10 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
Minz_Error::error(403);
}
+ if (self::reauthRedirect()) {
+ return;
+ }
+
$username = Minz_Request::paramString('username');
if (!FreshRSS_UserDAO::exists($username)) {
Minz_Error::error(404);
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index 5c861f1db..19cd26aa5 100644
--- a/app/Models/Auth.php
+++ b/app/Models/Auth.php
@@ -165,6 +165,7 @@ class FreshRSS_Auth {
self::$login_ok = false;
Minz_Session::_params([
'loginOk' => false,
+ 'lastReauth' => false,
'csrf' => false,
'REMOTE_USER' => false,
]);
@@ -230,4 +231,54 @@ class FreshRSS_Auth {
}
return $token != '' && $token === $csrf;
}
+
+ public static function needsReauth(): bool {
+ $auth_type = FreshRSS_Context::systemConf()->auth_type;
+ $reauth_required = FreshRSS_Context::systemConf()->reauth_required;
+ $reauth_time = FreshRSS_Context::systemConf()->reauth_time;
+
+ if (!$reauth_required) {
+ return false;
+ }
+
+ $last_reauth = Minz_Session::paramInt('lastReauth');
+
+ if ($auth_type !== 'none' && time() - $last_reauth > $reauth_time) {
+ if ($auth_type === 'http_auth') {
+ // TODO: not implemented - just let the user through
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return if user needs reauth and got redirected to login page.
+ *
+ * @param array{c?: string, a?: string, params?: array<string, mixed>}|null $redirect
+ */
+ public static function requestReauth(?array $redirect = null): bool {
+ if (self::needsReauth()) {
+ if (Minz_Request::paramBoolean('ajax')) {
+ // Send 403 and exit instead of redirect with Minz_Error::error()
+ header('HTTP/1.1 403 Forbidden');
+ exit();
+ }
+
+ $redirect = Minz_Url::serialize($redirect ?? Minz_Request::currentRequest());
+
+ Minz_Request::forward([
+ 'c' => 'auth',
+ 'a' => 'reauth',
+ 'params' => [
+ 'r' => $redirect,
+ ],
+ ], true);
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/app/Models/SystemConfiguration.php b/app/Models/SystemConfiguration.php
index 403950728..7c7862b8a 100644
--- a/app/Models/SystemConfiguration.php
+++ b/app/Models/SystemConfiguration.php
@@ -9,6 +9,8 @@ declare(strict_types=1);
* @property bool $api_enabled
* @property string $archiving
* @property 'form'|'http_auth'|'none' $auth_type
+ * @property-read bool $reauth_required
+ * @property-read int $reauth_time
* @property-read string $auto_update_url
* @property-read array<int,mixed> $curl_options
* @property string $default_user
diff --git a/app/i18n/cs/gen.php b/app/i18n/cs/gen.php
index 173b2c2d7..232251ab2 100644
--- a/app/i18n/cs/gen.php
+++ b/app/i18n/cs/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Heslo',
'format' => '<small>Alespoň 7 znaků</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nový účet',
'ask' => 'Vytvořit účet?',
diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php
index d21b1a25a..b373b38f2 100644
--- a/app/i18n/de/gen.php
+++ b/app/i18n/de/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Passwort',
'format' => '<small>mindestens 7 Zeichen</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Neuer Account',
'ask' => 'Erstelle einen Account?',
diff --git a/app/i18n/el/gen.php b/app/i18n/el/gen.php
index 5c9daf67b..a8a22e699 100644
--- a/app/i18n/el/gen.php
+++ b/app/i18n/el/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Password', // TODO
'format' => '<small>At least 7 characters</small>', // TODO
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'New account', // TODO
'ask' => 'Create an account?', // TODO
diff --git a/app/i18n/en-us/gen.php b/app/i18n/en-us/gen.php
index b233595fe..f272f9d12 100644
--- a/app/i18n/en-us/gen.php
+++ b/app/i18n/en-us/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Password', // IGNORE
'format' => '<small>At least 7 characters</small>', // IGNORE
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // IGNORE
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // IGNORE
+ 'title' => 'Reauthentication', // IGNORE
+ ),
'registration' => array(
'_' => 'New account', // IGNORE
'ask' => 'Create an account?', // IGNORE
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 0f3e5aff7..398e7e60b 100644
--- a/app/i18n/en/gen.php
+++ b/app/i18n/en/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Password',
'format' => '<small>At least 7 characters</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required',
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>',
+ 'title' => 'Reauthentication',
+ ),
'registration' => array(
'_' => 'New account',
'ask' => 'Create an account?',
diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php
index c7dae3b64..bca993c13 100644
--- a/app/i18n/es/gen.php
+++ b/app/i18n/es/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Contraseña',
'format' => '<small>Mínimo de 7 caracteres</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nueva cuenta',
'ask' => '¿Crear una cuenta?',
diff --git a/app/i18n/fa/gen.php b/app/i18n/fa/gen.php
index bce9f38bc..33f3056cc 100644
--- a/app/i18n/fa/gen.php
+++ b/app/i18n/fa/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => ' رمز عبور',
'format' => '<small>حداقل 7 نویسه</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => ' حساب جدید',
'ask' => ' یک حساب کاربری ایجاد کنید؟',
diff --git a/app/i18n/fi/gen.php b/app/i18n/fi/gen.php
index 3860055ac..b8a8ad905 100644
--- a/app/i18n/fi/gen.php
+++ b/app/i18n/fi/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Salasana',
'format' => '<small>Vähintään 7 merkkiä</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Uusi tili',
'ask' => 'Haluatko luoda tilin?',
diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php
index 9706e57fc..282fc82c9 100644
--- a/app/i18n/fr/gen.php
+++ b/app/i18n/fr/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Mot de passe',
'format' => '<small>7 caractères minimum</small>',
),
+ 'reauth' => array(
+ 'header' => 'Une réauthentification est requise',
+ 'tip' => 'La réauthentification sera valide pendant <u>%d minutes</u>',
+ 'title' => 'Réauthentification',
+ ),
'registration' => array(
'_' => 'Nouveau compte',
'ask' => 'Créer un compte ?',
diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php
index 7af4b4ce0..1ce9b7bdb 100644
--- a/app/i18n/he/gen.php
+++ b/app/i18n/he/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'סיסמה',
'format' => '<small>At least 7 characters</small>', // TODO
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'New account', // TODO
'ask' => 'Create an account?', // TODO
diff --git a/app/i18n/hu/gen.php b/app/i18n/hu/gen.php
index 891ae3695..54a2eaf56 100755
--- a/app/i18n/hu/gen.php
+++ b/app/i18n/hu/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Jelszó',
'format' => '<small>Legalább 7 karakter hosszú</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Új fiók',
'ask' => 'Létrehoz egy új fiókot?',
diff --git a/app/i18n/id/gen.php b/app/i18n/id/gen.php
index eaee65687..d62166902 100644
--- a/app/i18n/id/gen.php
+++ b/app/i18n/id/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Kata sandi',
'format' => '<small>Paling tidak 7 karakter</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Akun baru',
'ask' => 'Buat akun?',
diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php
index d2ed49ee4..485f51371 100644
--- a/app/i18n/it/gen.php
+++ b/app/i18n/it/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Password', // IGNORE
'format' => '<small>almeno 7 caratteri</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nuovo profilo',
'ask' => 'Vuoi creare un nuovo profilo?',
diff --git a/app/i18n/ja/gen.php b/app/i18n/ja/gen.php
index bc494802c..5debea06f 100644
--- a/app/i18n/ja/gen.php
+++ b/app/i18n/ja/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'パスワード',
'format' => '<small>最低7文字必要です</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => '新規アカウント',
'ask' => 'アカウントを作りますか?',
diff --git a/app/i18n/ko/gen.php b/app/i18n/ko/gen.php
index b8f8318ba..803f02ff9 100644
--- a/app/i18n/ko/gen.php
+++ b/app/i18n/ko/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => '암호',
'format' => '<small>7 글자 이상이어야 합니다</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => '새 계정',
'ask' => '새 계정을 만들까요?',
diff --git a/app/i18n/lv/gen.php b/app/i18n/lv/gen.php
index 61f06f610..dd3ed807f 100644
--- a/app/i18n/lv/gen.php
+++ b/app/i18n/lv/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Parole',
'format' => '<small>Vismaz 7 rakstzīmes</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Jauns konts',
'ask' => 'Uztaisīt kontu?',
diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php
index 351024019..d259edb30 100644
--- a/app/i18n/nl/gen.php
+++ b/app/i18n/nl/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Wachtwoord',
'format' => '<small>Ten minste 7 tekens</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nieuw account',
'ask' => 'Maak een account?',
diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php
index 3f5cdeb88..22d9918e7 100644
--- a/app/i18n/oc/gen.php
+++ b/app/i18n/oc/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Senhal',
'format' => '<small>Almens 7 caractèrs</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Compte nòu',
'ask' => 'Crear un compte?',
diff --git a/app/i18n/pl/gen.php b/app/i18n/pl/gen.php
index f59926574..4630abdf3 100644
--- a/app/i18n/pl/gen.php
+++ b/app/i18n/pl/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Hasło',
'format' => '<small>przynajmniej 7 znaków</small>',
),
+ 'reauth' => array(
+ 'header' => 'Wymagane ponowne logowanie',
+ 'tip' => 'Nie będziesz proszony o ponowne logowanie przez <u>%d minut</u>',
+ 'title' => 'Ponowne logowanie',
+ ),
'registration' => array(
'_' => 'Tworzenie konta',
'ask' => 'Nie masz konta?',
diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php
index 4711146b2..77e3acb5f 100644
--- a/app/i18n/pt-br/gen.php
+++ b/app/i18n/pt-br/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Senha',
'format' => '<small>Ao menos 7 caracteres</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nova conta',
'ask' => 'Criar novoa conta?',
diff --git a/app/i18n/pt-pt/gen.php b/app/i18n/pt-pt/gen.php
index 104b61636..893a174aa 100644
--- a/app/i18n/pt-pt/gen.php
+++ b/app/i18n/pt-pt/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Senha',
'format' => '<small>Pelo menos 7 caracteres</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nova conta',
'ask' => 'Criar novoa conta?',
diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php
index e11b1cdc4..68bfa7cba 100644
--- a/app/i18n/ru/gen.php
+++ b/app/i18n/ru/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Пароль',
'format' => '<small>Не менее 7 символов</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Новый аккаунт',
'ask' => 'Создать аккаунт?',
diff --git a/app/i18n/sk/gen.php b/app/i18n/sk/gen.php
index b8df72a16..54e8c8cb2 100644
--- a/app/i18n/sk/gen.php
+++ b/app/i18n/sk/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Heslo',
'format' => '<small>Najmenej 7 znakov</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Nový účet',
'ask' => 'Vytvoriť účet?',
diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php
index 99ab450fb..3525cd6b0 100644
--- a/app/i18n/tr/gen.php
+++ b/app/i18n/tr/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => 'Parola',
'format' => '<small>En az 7 karakter</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => 'Yeni hesap',
'ask' => 'Hesap oluştur?',
diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php
index a0ba4bab8..ec910a012 100644
--- a/app/i18n/zh-cn/gen.php
+++ b/app/i18n/zh-cn/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => '密码',
'format' => '<small>至少 7 个字符</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => '新用户',
'ask' => '创建新用户?',
diff --git a/app/i18n/zh-tw/gen.php b/app/i18n/zh-tw/gen.php
index 4b0e0feab..82e70b730 100644
--- a/app/i18n/zh-tw/gen.php
+++ b/app/i18n/zh-tw/gen.php
@@ -61,6 +61,11 @@ return array(
'_' => '密碼',
'format' => '<small>至少 7 個字元</small>',
),
+ 'reauth' => array(
+ 'header' => 'Reauthentication is required', // TODO
+ 'tip' => 'You won’t be asked to sign in again for <u>%d minutes</u>', // TODO
+ 'title' => 'Reauthentication', // TODO
+ ),
'registration' => array(
'_' => '新使用者',
'ask' => '創建新使用者?',
diff --git a/app/views/auth/reauth.phtml b/app/views/auth/reauth.phtml
new file mode 100644
index 000000000..d5b75decd
--- /dev/null
+++ b/app/views/auth/reauth.phtml
@@ -0,0 +1,32 @@
+<?php
+ declare(strict_types=1);
+ /** @var FreshRSS_View $this */
+?>
+
+<main class="prompt">
+ <h1><?= _t('gen.auth.reauth.header') ?></h1>
+
+ <form id="crypto-form" method="post">
+ <input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+ <input type="hidden" id="username" value="<?= Minz_User::name() ?>" />
+ <div class="form-group">
+ <label for="passwordPlain"><?= _t('gen.auth.password') ?></label>
+ <div class="stick">
+ <input type="password" id="passwordPlain" required="required" />
+ <button type="button" class="btn toggle-password" data-toggle="passwordPlain"><?= _i('key') ?></button>
+ </div>
+ <input type="hidden" id="challenge" name="challenge" />
+ <noscript><strong><?= _t('gen.js.should_be_activated') ?></strong></noscript>
+ </div>
+ <?php
+ $reauth_time = FreshRSS_Context::systemConf()->reauth_time;
+ ?>
+ <p class="help"><?= _i('help') ?> <?= _t('gen.auth.reauth.tip', intval($reauth_time / 60)) ?></p>
+ <div class="form-group form-group-actions">
+ <button id="loginButton" type="submit" class="btn btn-important" disabled="disabled">
+ <?= _t('gen.auth.login') ?>
+ </button>
+ </div>
+ </form>
+</main>
+
diff --git a/config.default.php b/config.default.php
index f42fdc215..c839c9a3c 100644
--- a/config.default.php
+++ b/config.default.php
@@ -59,6 +59,13 @@ return [
# and in particular not protect `/FreshRSS/p/api/` if you would like to use the API (different login system).
'auth_type' => 'form',
+ # Whether reauthentication is required for performing sensitive actions e.g. promoting a user or applying an update
+ 'reauth_required' => true,
+
+ # Time before asking for reauth
+ # Default: 1200s (20 min)
+ 'reauth_time' => 1200,
+
# When using http_auth, automatically register any unknown user
'http_auth_auto_register' => true,
diff --git a/docs/i18n/flags/gen/de.svg b/docs/i18n/flags/gen/de.svg
index e159e6658..b2806e1ac 100644
--- a/docs/i18n/flags/gen/de.svg
+++ b/docs/i18n/flags/gen/de.svg
@@ -2,6 +2,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
<rect rx="3" width="70" height="20" fill="green" />
- <text x="34" y="14">🇩🇪 96%</text>
+ <text x="34" y="14">🇩🇪 95%</text>
</g>
</svg> \ No newline at end of file
diff --git a/docs/i18n/flags/gen/id.svg b/docs/i18n/flags/gen/id.svg
index 45745ee5e..6e2a1e6d0 100644
--- a/docs/i18n/flags/gen/id.svg
+++ b/docs/i18n/flags/gen/id.svg
@@ -2,6 +2,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
<rect rx="3" width="70" height="20" fill="green" />
- <text x="34" y="14">🇮🇩 99%</text>
+ <text x="34" y="14">🇮🇩 98%</text>
</g>
</svg> \ No newline at end of file
diff --git a/lib/core-extensions/UserJS/extension.php b/lib/core-extensions/UserJS/extension.php
index db27b9ebd..e14827450 100644
--- a/lib/core-extensions/UserJS/extension.php
+++ b/lib/core-extensions/UserJS/extension.php
@@ -21,6 +21,10 @@ final class UserJSExtension extends Minz_Extension {
$this->registerTranslates();
+ if (FreshRSS_Auth::requestReauth()) {
+ return;
+ }
+
if (Minz_Request::isPost()) {
$js_rules = Minz_Request::paramString('js-rules', plaintext: true);
$this->saveFile(self::FILENAME, $js_rules);
diff --git a/lib/core-extensions/UserJS/metadata.json b/lib/core-extensions/UserJS/metadata.json
index 9096d753d..5a82753f0 100644
--- a/lib/core-extensions/UserJS/metadata.json
+++ b/lib/core-extensions/UserJS/metadata.json
@@ -2,7 +2,7 @@
"name": "User JS",
"author": "hkcomori, Frans de Jonge",
"description": "Apply user JS.",
- "version": "1.1.0",
+ "version": "1.1.1",
"entrypoint": "UserJS",
"type": "user"
}
diff --git a/p/scripts/extra.js b/p/scripts/extra.js
index c94d004ce..5a77aee4b 100644
--- a/p/scripts/extra.js
+++ b/p/scripts/extra.js
@@ -327,6 +327,11 @@ function open_slider_listener(ev) {
req.open('GET', ahref, true);
req.responseType = 'document';
req.onload = function (e) {
+ if (this.status === 403) {
+ // Redirect to reauth page (or fail if session expired)
+ location.href = a.href;
+ return;
+ }
location.href = '#slider'; // close menu/dropdown
document.documentElement.classList.add('slider-active');
slider.classList.add('active');