From b3b790d4244c105c3861ff95a6945d9c3053a2bf Mon Sep 17 00:00:00 2001 From: Sarjuuk Date: Thu, 7 Aug 2025 01:38:03 +0200 Subject: [PATCH] Template/Update (Part 11) * convert signin/signout functionality * implement 'log out all devices' option --- endpoints/account/signin.php | 141 ++++++++++++++++++ endpoints/account/signout.php | 40 +++++ localization/locale_dede.php | 4 +- localization/locale_enus.php | 4 +- localization/locale_eses.php | 4 +- localization/locale_frfr.php | 4 +- localization/locale_ruru.php | 4 +- localization/locale_zhcn.php | 2 + pages/account.php | 74 --------- static/js/global.js | 3 +- static/js/locale_dede.js | 1 + static/js/locale_enus.js | 1 + static/js/locale_eses.js | 3 +- static/js/locale_frfr.js | 1 + static/js/locale_ruru.js | 1 + static/js/locale_zhcn.js | 1 + .../inputbox-form-signin.tpl.php} | 41 ++--- 17 files changed, 223 insertions(+), 106 deletions(-) create mode 100644 endpoints/account/signin.php create mode 100644 endpoints/account/signout.php rename template/{pages/acc-signIn.tpl.php => bricks/inputbox-form-signin.tpl.php} (65%) diff --git a/endpoints/account/signin.php b/endpoints/account/signin.php new file mode 100644 index 00000000..b0fe5a6e --- /dev/null +++ b/endpoints/account/signin.php @@ -0,0 +1,141 @@ + ['filter' => FILTER_CALLBACK, 'options' => [Util::class, 'validateLogin'] ], + 'password' => ['filter' => FILTER_CALLBACK, 'options' => [Util::class, 'validatePassword']], + 'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkRememberMe'] ] + ); + protected array $expectedGET = array( + 'token' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{32}$/']], + 'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW ] + ); + + private bool $success = false; + + public function __construct() + { + // if the user is logged in, goto user dashboard + if (User::isLoggedIn()) + $this->forward('?user='.User::$username); + + parent::__construct(); + } + + protected function generate() : void + { + $username = ''; + $message = ''; + + $this->title = [Lang::account('title')]; + + if ($this->_get['token']) + { + // coming from username recovery, prefill username + if ($_ = DB::Aowow()->selectCell('SELECT `login` FROM ?_account WHERE `status` IN (?a) AND `token` = ? AND `statusTimer` > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token'])) + $username = $_; + } + + $message = $this->doSignIn(); + if (!$this->success) + User::destroy(); + else + $this->forward($this->getNext(true)); + + $this->inputbox = ['inputbox-form-signin', array( + 'head' => Lang::account('doSignIn'), + 'action' => '?account=signin&next='.$this->getNext(), + 'error' => $message, + 'username' => $username, + 'rememberMe' => !!$this->_post['remember_me'], + 'hasRecovery' => Cfg::get('ACC_EXT_RECOVER_URL') || Cfg::get('ACC_AUTH_MODE') == AUTH_MODE_SELF, + )]; + + parent::generate(); + } + + private function doSignIn() : string + { + if (is_null($this->_post['username']) && is_null($this->_post['password'])) + return ''; + + if (!$this->assertPOST('username')) + return Lang::account('userNotFound'); + + if (!$this->assertPOST('password')) + return Lang::account('wrongPass'); + + $error = match (User::authenticate($this->_post['username'], $this->_post['password'])) + { + AUTH_OK, AUTH_BANNED => $this->onAuthSuccess(), + // AUTH_BANNED => Lang::account('accBanned'); // ToDo: should this return an error? the actual account functionality should be blocked elsewhere + AUTH_WRONGUSER => Lang::account('userNotFound'), + AUTH_WRONGPASS => Lang::account('wrongPass'), + AUTH_IPBANNED => Lang::account('loginExceeded', [Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)]), + AUTH_INTERNAL_ERR => Lang::main('intError'), + default => Lang::main('intError') + }; + + if (!$error) + $this->success = true; + + return $error; + } + + private function onAuthSuccess() : string + { + if (!User::$ip) + { + trigger_error('AccountSigninResponse::onAuthSuccess() - tried to login user without ip set', E_USER_ERROR); + return Lang::main('intError'); + } + + $email = filter_var($this->_post['username'], FILTER_VALIDATE_EMAIL); + + // reset account status, update expiration + $ok = DB::Aowow()->query('UPDATE ?_account SET `prevIP` = IF(`curIp` = ?, `prevIP`, `curIP`), `curIP` = IF(`curIp` = ?, `curIP`, ?), `status` = IF(`status` = ?d, `status`, 0), `statusTimer` = IF(`status` = ?d, `statusTimer`, 0), `token` = IF(`status` = ?d, `token`, "") WHERE { `email` = ? } { `login` = ? }', + User::$ip, User::$ip, User::$ip, + ACC_STATUS_NEW, ACC_STATUS_NEW, ACC_STATUS_NEW, + $email ?: DBSIMPLE_SKIP, + !$email ? $this->_post['username'] : DBSIMPLE_SKIP + ); + + if (!is_int($ok)) // num updated fields or null on fail + { + trigger_error('AccountSigninResponse::onAuthSuccess() - failed to update account status', E_USER_ERROR); + return Lang::main('intError'); + } + + session_regenerate_id(true); // user status changed => regenerate id + + // create new session entry + DB::Aowow()->query('INSERT INTO ?_account_sessions (`userId`, `sessionId`, `created`, `expires`, `touched`, `deviceInfo`, `ip`, `status`) VALUES (?d, ?, ?d, ?d, ?d, ?, ?, ?d)', + User::$id, session_id(), time(), $this->_post['remember_me'] ? 0 : time() + Cfg::get('SESSION_TIMEOUT_DELAY'), time(), User::$agent, User::$ip, SESSION_ACTIVE); + + if (User::init()) // reinitialize the user + User::save(); + + return ''; + } +} + +?> diff --git a/endpoints/account/signout.php b/endpoints/account/signout.php new file mode 100644 index 00000000..7447f92e --- /dev/null +++ b/endpoints/account/signout.php @@ -0,0 +1,40 @@ + ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH], + 'global' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkEmptySet'] ] + ); + + public function __construct(string $pageParam) + { + // if the user not is logged in goto login page + if (!User::isLoggedIn()) + $this->forwardToSignIn(); + + parent::__construct($pageParam); + } + + protected function generate() : void + { + if ($this->_get['global']) + DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `userId` = ?d', time(), SESSION_FORCED_LOGOUT, User::$id); + else + DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `sessionId` = ?', time(), SESSION_LOGOUT, session_id()); + + User::destroy(); + + $this->redirectTo = $this->getNext(true); + } +} + +?> diff --git a/localization/locale_dede.php b/localization/locale_dede.php index 5dcbda6b..958b3d1f 100644 --- a/localization/locale_dede.php +++ b/localization/locale_dede.php @@ -885,7 +885,7 @@ $lang = array( "Screenshot-Verwalter", "Video-Verwalter", "API-Partner", "Ausstehend" ), // signIn - 'doSignIn' => "Mit Eurem AoWoW-Konto anmelden", + 'doSignIn' => "Mit Eurem Konto anmelden", 'signIn' => "Anmelden", 'user' => "Benutzername", 'pass' => "Kennwort", @@ -894,6 +894,8 @@ $lang = array( 'forgotUser' => "Benutzername", 'forgotPass' => "Kennwort", 'accCreate' => 'Noch kein Konto? Jetzt eins erstellen!', + 'resendMail' => "Bestätigungsmail erneut senden", + 'resendHint' => "Wenn Sie sich registriert haben, aber keine Bestätigungs-E-Mail erhalten haben, geben Sie Ihre E-Mail-Adresse unten ein und senden Sie das Formular ab. (Bitte überprüfen Sie Ihre Spam- oder Papierkorb-Ordner, um sicherzustellen, dass die E-Mail nicht versehentlich an der falschen Stelle abgelegt wurde!)", // recovery 'recoverUser' => "Benutzernamenanfrage", diff --git a/localization/locale_enus.php b/localization/locale_enus.php index 29e962db..2d0d0b9e 100644 --- a/localization/locale_enus.php +++ b/localization/locale_enus.php @@ -885,7 +885,7 @@ $lang = array( "Screenshot manager", "Video manager", "API partner", "Pending" ), // signIn - 'doSignIn' => "Log in to your AoWoW Account", + 'doSignIn' => "Log in to your Account", 'signIn' => "Log In", 'user' => "Username", 'pass' => "Password", @@ -894,6 +894,8 @@ $lang = array( 'forgotUser' => "Username", 'forgotPass' => "Password", 'accCreate' => 'Don\'t have an account? Create one now!', + 'resendMail' => "Re-Send Verification Email", + 'resendHint' => "If you registered but did not receive a verification email, enter your email address below and submit the form. (Please be sure to check your spam or trash folders to make sure the email didn't accidentally get put in the wrong place!)", // recovery 'recoverUser' => "Username Request", diff --git a/localization/locale_eses.php b/localization/locale_eses.php index 06a2be06..fa17ca26 100644 --- a/localization/locale_eses.php +++ b/localization/locale_eses.php @@ -885,7 +885,7 @@ $lang = array( "Gestor de Capturas de pantalla","Gestor de vídeos", "Partner de API", "Pendiente" ), // signIn - 'doSignIn' => "Iniciar sesión con tu cuenta de Aowow", + 'doSignIn' => "Iniciar sesión con tu cuenta", 'signIn' => "Iniciar sesión", 'user' => "Nombre de usuario", 'pass' => "Contraseña", @@ -894,6 +894,8 @@ $lang = array( 'forgotUser' => "Nombre de usuario", 'forgotPass' => "Contraseña", 'accCreate ' => '¿No tienes una cuenta? ¡Crea una ahora!', + 'resendMail' => "Reenviar correo de verificación", + 'resendHint' => "Si te has registrado pero no recibiste un correo de verificación, introduce tu dirección de correo más abajo y completa el formulario. (¡Por favor, asegúrate de comprobar tus directorios de correo no deseado o papelera por si el correo acabara en el lugar equivocado!)", // recovery 'recoverUser' => "Pedir nombre de usuario", diff --git a/localization/locale_frfr.php b/localization/locale_frfr.php index ddd0d827..49e972ae 100644 --- a/localization/locale_frfr.php +++ b/localization/locale_frfr.php @@ -885,7 +885,7 @@ $lang = array( "Gestionnaire de capture d'écran","Gestionnaire de vidéos", "Partenaire API", "En attente" ), // signIn - 'doSignIn' => "Connexion à votre compte Aowow", + 'doSignIn' => "Connexion à votre compte", 'signIn' => "Connexion", 'user' => "Nom d'utilisateur", 'pass' => "Mot de passe", @@ -894,6 +894,8 @@ $lang = array( 'forgotUser' => "Nom d'utilisateur", 'forgotPass' => "Mot de passe", 'accCreate' => 'Vous n\'avez pas encore de compte ? Créez-en un maintenant !', + 'resendMail' => "Renvoyer le courriel de vérification", + 'resendHint' => "Si vous vous êtes enregistré mais n'avez pas reçu de courriel de vérification, entrez votre adresse électronique ci-dessous et validez le formulaire. (Assurez-vous de vérifier vos dossiers de courrier indésirable et votre corbeille pour vous assurer que le courriel ne s'y soit pas perdu !)", // recovery 'recoverUser' => "Demande de nom d'utilisateur", diff --git a/localization/locale_ruru.php b/localization/locale_ruru.php index ced89286..888096ff 100644 --- a/localization/locale_ruru.php +++ b/localization/locale_ruru.php @@ -885,7 +885,7 @@ $lang = array( "Менеджер изображений", "Менеджер видео", "API партнер", "Ожидающее" ), // signIn - 'doSignIn' => "Войти в вашу учетную запись Aowow", + 'doSignIn' => "Войти в вашу учетную запись", 'signIn' => "Вход", 'user' => "Логин", 'pass' => "Пароль", @@ -894,6 +894,8 @@ $lang = array( 'forgotUser' => "Имя пользователя", 'forgotPass' => "Пароль", 'accCreate' => 'У вас еще нет учетной записи? Зарегистрируйтесь прямо сейчас!', + 'resendMail' => "Вновь выслать верификационное письмо", + 'resendHint' => "Если вы зарегистрировались, но не получили проверочного письма, пожалуйста, введите ваш email адрес ниже и подтвердите отправку формы. (Пожалуйста, удостоверьтесь, что Вы проверили папку со спамом и/или корзину Вашего почтового сервиса)", // recovery 'recoverUser' => "Запрос имени пользователя", diff --git a/localization/locale_zhcn.php b/localization/locale_zhcn.php index 3fb37d58..78690188 100644 --- a/localization/locale_zhcn.php +++ b/localization/locale_zhcn.php @@ -894,6 +894,8 @@ $lang = array( 'forgotUser' => "用户名", 'forgotPass' => "密码", 'accCreate' => '没有账号?现在创建一个!', + 'resendMail' => "重新发送验证邮件", + 'resendHint' => "[If you registered but did not receive a verification email, enter your email address below and submit the form. (Please be sure to check your spam or trash folders to make sure the email didn't accidentally get put in the wrong place!)]", // recovery 'recoverUser' => "用户名需求", diff --git a/pages/account.php b/pages/account.php index 6ab78328..2b39a866 100644 --- a/pages/account.php +++ b/pages/account.php @@ -25,9 +25,7 @@ class AccountPage extends GenericPage protected $mode = CACHE_TYPE_NONE; protected $category = null; protected $validCats = array( - 'signin' => [false], 'signup' => [false], - 'signout' => [true], 'forgotpassword' => [false], 'forgotusername' => [false] ); @@ -128,20 +126,6 @@ class AccountPage extends GenericPage } $this->head = Lang::account('recoverUser'); - break; - case 'signin': - $this->tpl = 'acc-signIn'; - $this->next = $this->getNext(); - if ($this->_post['username'] || $this->_post['password']) - { - if ($err = $this->doSignIn()) - $this->error = $err; - else - header('Location: '.$this->getNext(true), true, 302); - } - else if ($this->_get['token'] && ($_ = DB::Aowow()->selectCell('SELECT `username` FROM ?_account WHERE `status` IN (?a) AND `token` = ? AND `statusTimer` > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token']))) - $this->user = $_; - break; case 'signup': if (!Cfg::get('ACC_ALLOW_REGISTER')) @@ -180,10 +164,6 @@ class AccountPage extends GenericPage $this->head = sprintf(Lang::account('register'), $nStep); break; - case 'signout': - DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `sessionId` = ?', time(), SESSION_LOGOUT, session_id()); - - User::destroy(); default: header('Location: '.$this->getNext(true), true, 302); break; @@ -347,60 +327,6 @@ Markup.printHtml("description text here", "description-generic", { allow: Markup return false; } - private function doSignIn() - { - // check username - if (!User::isValidName($this->_post['username'])) - return Lang::account('userNotFound'); - - // check password - if (!User::isValidPass($this->_post['password'])) - return Lang::account('wrongPass'); - - switch (User::authenticate($this->_post['username'], $this->_post['password'])) - { - case AUTH_OK: - if (!User::$ip) - return Lang::main('intError'); - - // reset account status, update expiration - DB::Aowow()->query('UPDATE ?_account SET `prevIP` = IF(`curIp` = ?, `prevIP`, `curIP`), `curIP` = IF(`curIp` = ?, `curIP`, ?), `status` = IF(`status` = ?d, `status`, 0), `statusTimer` = IF(`status` = ?d, `statusTimer`, 0), `token` = IF(`status` = ?d, `token`, "") WHERE LOWER(`username`) = LOWER(?)', - User::$ip, User::$ip, User::$ip, - ACC_STATUS_NEW, ACC_STATUS_NEW, ACC_STATUS_NEW, - $this->_post['username'] - ); - - session_regenerate_id(true); // user status changed => regenerate id - - // create new session entry - DB::Aowow()->query('INSERT INTO ?_account_sessions (`userId`, `sessionId`, `created`, `expires`, `touched`, `deviceInfo`, `ip`, `status`) VALUES (?d, ?, ?d, ?d, ?d, ?, ?, ?d)', - User::$id, session_id(), time(), $this->_post['remember_me'] ? 0 : time() + Cfg::get('SESSION_TIMEOUT_DELAY'), time(), User::$agent, User::$ip, SESSION_ACTIVE); - - if (User::init()) // reinitialize the user - User::save(); - - return; - case AUTH_BANNED: - if (User::init()) - User::save(); - return Lang::account('accBanned'); - case AUTH_WRONGUSER: - User::destroy(); - return Lang::account('userNotFound'); - case AUTH_WRONGPASS: - User::destroy(); - return Lang::account('wrongPass'); - case AUTH_IPBANNED: - User::destroy(); - return sprintf(Lang::account('loginExceeded'), Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)); - case AUTH_INTERNAL_ERR: - User::destroy(); - return Lang::main('intError'); - default: - return; - } - } - private function doSignUp() { // check username diff --git a/static/js/global.js b/static/js/global.js index 9cdceb4b..91d49851 100644 --- a/static/js/global.js +++ b/static/js/global.js @@ -535,7 +535,8 @@ var PageTemplate = new function() } // Sign Out - menu.push(['sign-out', LANG.signout, '?account=signout']); + menu.push(['sign-out', LANG.signout, '?account=signout']); + menu.push(['sign-out-global', LANG.logOutEverywhere, '?account=signout&global']); Menu.add($link, menu); $link.addClass('hassubmenu'); diff --git a/static/js/locale_dede.js b/static/js/locale_dede.js index 28e1db9b..53b0745c 100644 --- a/static/js/locale_dede.js +++ b/static/js/locale_dede.js @@ -2605,6 +2605,7 @@ var LANG = { lastbluepost: "Letzter Blizzard-Forenbeitrag", level: "Stufe", location: "Standort", + logOutEverywhere: "Alle Geräte abmelden", losses: "Niederlagen", members: "Mitglieder", model: "Modell", diff --git a/static/js/locale_enus.js b/static/js/locale_enus.js index bddf3aa2..7c260ecf 100644 --- a/static/js/locale_enus.js +++ b/static/js/locale_enus.js @@ -2653,6 +2653,7 @@ var LANG = { lastbluepost: "Last blue post", level: "Level", location: "Location", + logOutEverywhere: "Log Out All Devices", losses: "Losses", members: "Members", model: "Model", diff --git a/static/js/locale_eses.js b/static/js/locale_eses.js index ba4b50d2..b720a44e 100644 --- a/static/js/locale_eses.js +++ b/static/js/locale_eses.js @@ -2605,6 +2605,7 @@ var LANG = { lastbluepost: "Último mensaje azul", level: "Nivel", location: "Lugar", + logOutEverywhere: "Desconectar de todos los dispositivos", losses: "Pérdidas", members: "Miembros", model: "Modelo", @@ -2654,7 +2655,7 @@ var LANG = { settings: "Configuración", side: "Lado", signature: "Firma", - signout: "Salir", + signout: "Cerrar sesión", sockets: "Ranuras", source: "Fuente", skill: "Habilidad", diff --git a/static/js/locale_frfr.js b/static/js/locale_frfr.js index 7cdaf084..99d18899 100644 --- a/static/js/locale_frfr.js +++ b/static/js/locale_frfr.js @@ -2605,6 +2605,7 @@ var LANG = { lastbluepost: "Dernier sujet de Blizzard", level: "Niveau", location: "Lieu", + logOutEverywhere: "Se déconnecter de tous les appareils", losses: "Pertes", members: "Membres", model: "Modèle", diff --git a/static/js/locale_ruru.js b/static/js/locale_ruru.js index 14c4a6e5..a9fa0801 100644 --- a/static/js/locale_ruru.js +++ b/static/js/locale_ruru.js @@ -2605,6 +2605,7 @@ var LANG = { lastbluepost: "Последний блю-пост", level: "Уровень", location: "Местонахождение", + logOutEverywhere: "Выйти на всех устройствах", losses: "Поражения", members: "Участники", model: "Модель", diff --git a/static/js/locale_zhcn.js b/static/js/locale_zhcn.js index eba3f02f..c21fdcbd 100644 --- a/static/js/locale_zhcn.js +++ b/static/js/locale_zhcn.js @@ -2652,6 +2652,7 @@ var LANG = { lastbluepost: "最新蓝贴", level: "等级", location: "地点", + logOutEverywhere: "全局登出", losses: "损失", members: "成员", model: "模型", diff --git a/template/pages/acc-signIn.tpl.php b/template/bricks/inputbox-form-signin.tpl.php similarity index 65% rename from template/pages/acc-signIn.tpl.php rename to template/bricks/inputbox-form-signin.tpl.php index 89d49a08..76876e57 100644 --- a/template/pages/acc-signIn.tpl.php +++ b/template/bricks/inputbox-form-signin.tpl.php @@ -1,16 +1,10 @@ - - -brick('header'); ?> - -
-
-
brick('announcement'); + namespace Aowow\Template; - $this->brick('pageTemplate'); + use \Aowow\Lang; ?>
+ -
+
-

-
error; ?>
+

+
- + - +
/>
@@ -56,20 +50,17 @@
-
-
|
+ +
+
| |
+
-'.Lang::account('accCreate')."
\n"; -endif; -?> +cfg('ACC_ALLOW_REGISTER')): ?> +
+ +
-
- - -brick('footer'); ?>