diff --git a/docker/www/Dockerfile b/docker/www/Dockerfile index 09adab86..e8b9eee6 100644 --- a/docker/www/Dockerfile +++ b/docker/www/Dockerfile @@ -9,9 +9,10 @@ ARG XDEBUG_AUTOSTART=1 ARG XDEBUG_REMOTE_LOG RUN apt-get update -y -RUN apt-get install -y --no-install-recommends libzip-dev zip unzip git libfreetype6-dev libjpeg62-turbo-dev libpng-dev +RUN apt-get install -y --no-install-recommends libzip-dev zip unzip git libfreetype6-dev libjpeg62-turbo-dev libpng-dev zlib1g-dev libicu-dev g++ -RUN docker-php-ext-install mysqli calendar mbstring +RUN docker-php-ext-install mysqli calendar mbstring intl +RUN docker-php-ext-configure intl RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && docker-php-ext-install -j$(nproc) gd RUN pecl install xdebug && docker-php-ext-enable xdebug && \ echo "xdebug.remote_enable=${XDEBUG_REMOTE_ENABLE}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \ diff --git a/forum/composer.json b/forum/composer.json index 9ae866c6..ce9e0b8e 100644 --- a/forum/composer.json +++ b/forum/composer.json @@ -1,6 +1,8 @@ { "name": "coderscommunity/forum", "require": { - "sentry/sentry": "^1.0" + "sentry/sentry": "^1.0", + "ext-mbstring": "*", + "ext-intl": "*" } } \ No newline at end of file diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/composer.json b/forum/qa-plugin/q2a-googleauthenticator-login/composer.json new file mode 100644 index 00000000..e78ef36b --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/composer.json @@ -0,0 +1,20 @@ +{ + "name": "event15/q2a-googleauthenticator-login", + "type": "library", + "license": "GPLv2", + "authors": [ + { + "name": "Marek Woś", + "email": "mwos@getresponse.com" + } + ], + "minimum-stability": "dev", + "autoload": { + "psr-4": { + "CodersCommunity\\": "src/" + } + }, + "require": { + "robthree/twofactorauth": "dev-master" + } +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/composer.lock b/forum/qa-plugin/q2a-googleauthenticator-login/composer.lock new file mode 100644 index 00000000..c8fcd17b --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/composer.lock @@ -0,0 +1,72 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ffe70c5dd85e6617331f0646a1302812", + "packages": [ + { + "name": "robthree/twofactorauth", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/RobThree/TwoFactorAuth.git", + "reference": "4da0739b3897d567194148718b1a007390864501" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/4da0739b3897d567194148718b1a007390864501", + "reference": "4da0739b3897d567194148718b1a007390864501", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobThree\\Auth\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Janssen", + "homepage": "http://robiii.me", + "role": "Developer" + } + ], + "description": "Two Factor Authentication", + "homepage": "https://github.com/RobThree/TwoFactorAuth", + "keywords": [ + "Authentication", + "MFA", + "Multi Factor Authentication", + "Two Factor Authentication", + "authenticator", + "authy", + "php", + "tfa" + ], + "time": "2020-05-07T16:27:40+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "robthree/twofactorauth": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/metadata.json b/forum/qa-plugin/q2a-googleauthenticator-login/metadata.json new file mode 100644 index 00000000..c526aaac --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/metadata.json @@ -0,0 +1,13 @@ +{ + "name": "Google Authenticator 2-factor authentication for Q2A", + "description": "This plugin provides a Google 2FA security for users on forum", + "version": "1.0", + "date": "2018-05-30", + "author": "Marek Woś", + "author_uri": "http://github.com/event15", + "license": "GPLv2", + "update_uri": "Web address for Q2A to check for updates", + "min_q2a": "1.7", + "min_php": "5.4", + "load_order": "Bootstrap moment in which the plugin will be loaded" +} \ No newline at end of file diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-admin.php b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-admin.php new file mode 100644 index 00000000..6d743cbf --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-admin.php @@ -0,0 +1,85 @@ + $saved ? qa_lang('plugin_2fa/saved_plugin_settings') : null, + 'fields' => [[ + 'type' => 'checkbox', + 'label' => qa_opt('googleauthenticator_login') ? + qa_lang('plugin_2fa/enabled_plugin') : + qa_lang('plugin_2fa/disabled_plugin'), + 'value' => qa_opt('googleauthenticator_login') ? true : false, + 'tags' => 'NAME="googleauthenticator_enable_plugin"' + ] + ], + 'buttons' => [[ + 'label' => qa_lang('plugin_2fa/save_settings'), + 'tags' => 'NAME="2fa_save_button"' + ] + ] + ]; + } +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-layer.php b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-layer.php new file mode 100644 index 00000000..9bc69735 --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-layer.php @@ -0,0 +1,267 @@ +isSecurityPage = $request === 'account/security'; + + parent::__construct($template, $content, $rooturl, $request); + } + + public function nav_list($navigation, $class, $level = null) + { + $isLoggedUser = qa_get_logged_in_userid(); + + if ($this->isSecurityPage) { + $this->content['navigation']['sub'] = qa_user_sub_navigation(qa_get_logged_in_handle(), 'account/security', true); + } + + $this->prepareNavigation($class, $isLoggedUser, $navigation); + + parent::nav_list($navigation, $class, $level); + } + + public function doctype() + { + parent::doctype(); + + if(QA_FINAL_EXTERNAL_USERS) { + return; + } + + /** + * TODO: + * 1. Przed utworzeniem 2fa należy poprosić o hasło użytkownika + * a) Kliknij włącz 2fa + * b) przekieruj na stronę z podaniem hasła + * - jeśli poprawne: przekieruj do strony z QR code + * - jeśli nie: wyloguj (?) lub przerwij akcję (po prostu poprosić jeszcze raz a jakby się pomylił np. 3 razy to wtedy można całkowicie wylogować dla bezpieczeństwa) + * c) poproś o kod z aplikacji + * - jeśli poprawne: dodaj 2fa do konta + * - jeśli nie: przenieś do początkowej strony nie włączając niczego ( jak ktoś raz źle poda, to chyba jeszcze nie powód aby wszystko anulować, po prostu poprosić jeszcze raz bez ponownego skanowania?) + * 2. Przed wyłączeniem 2fa należy poprosić o kod z aplikacji oraz o hasło użytkownika (flow podobne do powyższego) + * a) Kliknij wyłącz 2fa + * b) przekieruj na stronę z podaniem hasła ORAZ jednocześnie z kodem z aplikacji + * - jeśli poprawne: wyłączy 2fa dla konta + * - jeśli nie: przekieruje do początkowej strony; do rozważenia, czy wysłać mail z powiadomieniem, że ktoś próbował wyłączyć 2fa czy też notify + */ + if ($this->isSecurityPage && (bool) qa_opt('googleauthenticator_login')) { + $content = [ + 'tags' => 'method="post" action="/account/security"', + 'style' => 'wide', + 'title' => qa_lang('plugin_2fa/title') + ]; + + $content += $this->switchPluginState(); + + $this->content['form_2fa'] = $content; + qa_html_theme_base::doctype(); + } + + if ('account' === $this->request) { + $url = qa_path_html('account/security'); + $this->content['custom_2fa'] = << +

Ustawienia zabezpieczeń konta

+
+

Przejdź do strony zabezpieczeń

+
+ +EOF; + + qa_html_theme_base::doctype(); + } + } + + private function switchPluginState(): array + { + $userAccount = $this->getUser(); + [$userHasActiveTwoFactorAuth, $recoveryCode, $secret] = $this->prepareGoogleAuthenticator($userAccount); + + if (!$userHasActiveTwoFactorAuth) { + return $this->showFormToEnableTwoFactorAuth(); + } + + $result = $this->showFormWithButtonToDisableTwoFactorAuth( + $userAccount['2fa_change_date'], + (bool) $userAccount['2fa_enabled'] + ); + + if (isset($this->init) && false === (bool) $userAccount['2fa_enabled']) { + $result = $this->renderQRCode($secret, $recoveryCode, $result); + } + + return $result; + } + + private function showFormWithButtonToDisableTwoFactorAuth($date, bool $showDateField): array + { + if ($showDateField) { + $field = ['old' => [ + 'label' => qa_lang('plugin_2fa/plugin_is_enabled'), + 'tags' => 'name="oldpassword" disabled', + 'value' => (string) $date, + 'type' => 'input' + ]]; + } + + return [ + 'fields' => $field ?? [], + 'buttons' => [ + 'enable' => [ + 'label' => qa_lang('plugin_2fa/disable_plugin') + ] + ], + 'hidden' => [ + 'dodisable2fa' => '1', + 'code' => qa_get_form_security_code('2faform') + ] + ]; + } + + private function showFormToEnableTwoFactorAuth(): array + { + return [ + 'fields' => [ + 'old' => [ + 'label' => qa_lang('plugin_2fa/plugin_is_disabled'), + 'value' => '', + 'type' => 'static' + ] + ], + 'buttons' => [ + 'enable_2fa' => [ + 'label' => qa_lang('plugin_2fa/enable_plugin') + ] + ], + 'hidden' => [ + 'doenable2fa' => '1', + 'code' => qa_get_form_security_code('2faform') + ] + ]; + } + + private function renderQRCode($secret, $recoveryCode, array $result): array + { + $note = qa_lang_html('plugin_2fa/2fa_data_info'); + $note = str_replace( + ['{{ QR_CODE }}', '{{ SECRET }}', '{{ RECOVERY_CODE }}', '{{ ERROR_START }}', '{{ ERROR_END }}'], + [ + '
', + '
' . chunk_split($secret, 4, ' ') . '
', + '
' . $recoveryCode . '

', + '
', + '
' + ], + $note + ); + $result['fields'][] = [ + 'style' => 'tall', + 'type' => 'static', + 'note' => $note + ]; + + return $result; + } + + private function prepareNavigation(string $class, ?int $isLoggedUser, array &$navigation): void + { + if ($class === 'nav-sub' + && isset($isLoggedUser) + && (bool) qa_opt('googleauthenticator_login') + && !$this->isExcludedPage() + ) { + $navigation[] = [ + 'label' => 'Ustawienia zabezpieczeń konta', + 'url' => qa_path_html('account/security'), + 'selected' => $this->isSecurityPage + ]; + unset($navigation['polls']); + } + } + + private function isExcludedPage(): bool + { + return in_array(explode('/', qa_request())[0], [ + 'admin', 'messages', 'users', 'unanswered', 'updates' + ]); + } + + private function getUserQuery($userId): ?array + { + $users = qa_db_read_all_assoc( + qa_db_query_sub( + 'SELECT us.userid, us.2fa_enabled, us.email, us.handle, us.2fa_change_date, up.points FROM ^users us LEFT JOIN ^userpoints up ON us.userid = up.userid WHERE us.userid=$', + $userId['userid'] ?? $userId + ) + ); + + return empty($users) ? null : $users[0]; + } + + private function updateUserEnable2FA($userId, $isEnabled, $secret = null, $recoveryCode = null) + { + $time = $this->setTime(); + $result = qa_db_query_sub( + 'UPDATE ^users SET 2fa_enabled=#, 2fa_change_date=$, 2fa_secret=$, 2fa_recovery_code=$ WHERE userid=#', + $isEnabled, + $isEnabled ? $time : 0, + $secret, + $recoveryCode, + $userId['userid'] + ); + + if ((bool) $result) { + return $isEnabled; + } + + user_error(qa_lang('plugin_2fa/2fa_setup_error')); + } + + private function getUser(): ?array + { + $userId = qa_get_logged_in_userid(); + + if (!isset($userId)) { + qa_redirect('login'); + } + + return $this->getUserQuery($userId); + } + + private function setTime() + { + $time = new DateTime('now'); + $formatter = new IntlDateFormatter('pl_PL', IntlDateFormatter::SHORT, IntlDateFormatter::SHORT); + $formatter->setPattern('EEEE, dd MMMM yyyy, HH:mm:ss'); + + return $formatter->format($time); + } + + private function prepareGoogleAuthenticator(?array $userAccount): array + { + $userHasActiveTwoFactorAuth = $userAccount['2fa_enabled']; + + if (!empty(qa_post_text('code')) && (!qa_check_form_security_code('2faform', qa_post_text('code')))) { + qa_fatal_error('Invalid CSRF code'); + } + + if (false === (bool) $userAccount['2fa_enabled'] && qa_clicked('doenable2fa')) { + $this->init = new GoogleAuthenticator; + $this->init->createSecret(); + $recoveryCode = $this->init->getRandomRecoveryCode(); + $secret = $this->init->getSecret(); + $userHasActiveTwoFactorAuth = (bool) $this->updateUserEnable2FA($userAccount, true, $secret, $recoveryCode); + } elseif ((bool) $userAccount['2fa_enabled'] && qa_clicked('dodisable2fa')) { + $userHasActiveTwoFactorAuth = (bool) $this->updateUserEnable2FA($userAccount, false); + } + + return [$userHasActiveTwoFactorAuth ?? false, $recoveryCode ?? false, $secret ?? false]; + } +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-overrides.php b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-overrides.php new file mode 100644 index 00000000..dc03c2b1 --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-overrides.php @@ -0,0 +1,40 @@ + $topath, 'remember' => $inremember, 'handle' => $handle, 'login_code' => $factorCode]); + } + + qa_set_logged_in_user_base($userid, $handle, $remember, $source); + } + + qa_set_logged_in_user_base($userid, $handle, $remember, $source); +} \ No newline at end of file diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-page-login.php b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-page-login.php new file mode 100644 index 00000000..d2950f2c --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-googleauthenticator-page-login.php @@ -0,0 +1,120 @@ +prepareTwoFactorAuthForm(); + + return $content; + } + + $authenticationData = qa_db_read_all_assoc( + qa_db_query_sub('SELECT 2fa_login_code, 2fa_secret FROM ^users WHERE handle = $', $login) + )[0]; + + $secret = $authenticationData['2fa_secret']; + $init = new GoogleAuthenticator($secret); + + if (!isset($secret) && empty($secret)) { + qa_fatal_error(qa_lang('plugin_2fa/secret_error')); + } + + + if (($loginCode === $authenticationData['2fa_login_code']) && $init->verifyCode($code)) { + $userId = qa_db_read_all_assoc(qa_db_query_sub('SELECT userid FROM ^users WHERE handle = $', $login))[0]['userid']; + $this->login($userId, $login, (bool) qa_get('remember'), qa_get('redirect')); + + qa_db_query_sub('UPDATE ^users SET 2fa_login_code=null, 2fa_login_code_created=null WHERE userid=#', $userId); + } + + $recoveryCode = qa_db_read_all_assoc( + qa_db_query_sub('SELECT 2fa_recovery_code FROM ^users WHERE handle = $', $login) + )[0]['2fa_recovery_code']; + + if (($code === $recoveryCode) && ($loginCode === $authenticationData['2fa_login_code'])) { + qa_db_query_sub('UPDATE ^users SET 2fa_recovery_code = 0, 2fa_login_code = 0, 2fa_login_code_created = 0 WHERE handle = $', $login); + $userId = qa_db_read_all_assoc(qa_db_query_sub('SELECT userid FROM ^users WHERE handle = $', $login))[0]['userid']; + $this->login($userId, $login, (bool) qa_get('remember'), 'account/security?restore=1'); + } + + $content = qa_content_prepare(); + $content['title'] = qa_lang('plugin_2fa/title'); + $content['form'] = $this->prepareTwoFactorAuthForm(); + $content['error'] = qa_lang('plugin_2fa/invalid_code'); + + return $content; + } + + private function login($userid, $login, $remember = false, $redirectPath = null): void + { + qa_set_session_user($userid, '2fa'); + + require_once QA_INCLUDE_DIR . 'db/selects.php'; + + $userinfo = qa_db_single_select(qa_db_user_account_selectspec($userid, true)); + if (empty($userinfo['sessioncode']) || ('2fa' !== $userinfo['sessionsource'])) { + $sessioncode = qa_db_user_rand_sessioncode(); + qa_db_user_set($userid, 'sessioncode', $sessioncode); + qa_db_user_set($userid, 'sessionsource', '2fa'); + } else { + $sessioncode = $userinfo['sessioncode']; + } + + qa_db_user_logged_in($userid, qa_remote_ip_address()); + qa_set_session_cookie($login, $sessioncode, $remember); + qa_report_event('u_login', $userid, $userinfo['handle'], qa_cookie_get()); + + $topath = qa_get('to') ?? $redirectPath; + + if (isset($topath)) { + qa_redirect_raw(qa_path_to_root() . $topath); + } + + qa_redirect(''); + } + + private function prepareTwoFactorAuthForm(): array + { + return [ + 'tags' => 'method="post" action="' . qa_self_html() . '"', + 'style' => 'wide', + 'title' => qa_lang('plugin_2fa/title'), + 'fields' => [ + 'old' => [ + 'label' => qa_lang('plugin_2fa/code_input'), + 'tags' => 'name="2fa_code"', + 'type' => 'input' + ], + ], + 'buttons' => [ + 'send' => [ + 'label' => qa_lang('plugin_2fa/send') + ] + ], + 'hidden' => [ + 'code' => qa_get_form_security_code('2faform'), + ] + ]; + } +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/q2a-user-security-page.php b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-user-security-page.php new file mode 100644 index 00000000..a381b2ec --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/q2a-user-security-page.php @@ -0,0 +1,61 @@ +directory = $directory; + $this->urltoroot = $urltoroot; + } + + public function match_request(string $request): bool + { + $this->requestParts = $request; + + return $request === 'account/security'; + } + + public function process_request(): ?array + { + $qa_content = qa_content_prepare(); + $qa_content['title'] = 'Ustawienia zabezpieczeń konta'; + + // logged in user id + $loggedIn = (int) qa_get_logged_in_userid(); + + if (empty($loggedIn)) { + $qa_content['error'] = qa_lang_html('block_pm/logged_in'); + + return $qa_content; + } + + $twoFactorIsEnabled = qa_db_read_all_assoc( + qa_db_query_sub('SELECT 2fa_enabled FROM ^users WHERE handle = $', qa_get_logged_in_handle()) + )[0]['2fa_enabled']; + + if (false === (bool) $twoFactorIsEnabled && null !== qa_get('restore') && 1 === (int) qa_get('restore')) { + $qa_content['error'] = qa_lang('plugin_2fa/recover_code_page_info'); + + return $qa_content; + } + + $qa_content['navigation']['sub'] = qa_user_sub_navigation($this->getUsername($loggedIn), '', false); + + return $qa_content; + } + + private function getUsername(int $userId) + { + return qa_db_read_one_value(qa_db_query_sub( + 'SELECT handle FROM ^users WHERE userid=#', + $userId + )); + } +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/qa-plugin.php b/forum/qa-plugin/q2a-googleauthenticator-login/qa-plugin.php new file mode 100644 index 00000000..14227acc --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/qa-plugin.php @@ -0,0 +1,36 @@ +siteName = qa_opt('site_title'); + $this->auth = new TwoFactorAuth($this->siteName, 6, 30, 'sha1'); + $this->secret = $secret; + $this->auth->ensureCorrectTime([ new LocalMachineTimeProvider ]); + } + + public function verifyCode($code): bool + { + return $this->auth->verifyCode($this->secret, $code, 10); + } + + public function createSecret(): void + { + $this->secret = $this->auth->createSecret(160); + } + + public function getSecret(): string + { + return $this->secret; + } + + public function getCode() + { + return $this->auth->getCode($this->secret); + } + + public function getQRCode(): string + { + return $this->auth->getQRCodeImageAsDataUri($this->siteName, $this->secret); + } + + public function getRandomRecoveryCode(): string + { + return $this->randomString(5) . '-' . $this->randomString(5); + } + + /** + * https://stackoverflow.com/a/31107425 + */ + public function randomString(int $length = 64, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string + { + if ($length < 1) { + throw new \RangeException("Length must be a positive integer"); + } + + $pieces = []; + $max = mb_strlen($keyspace, '8bit') - 1; + for ($i = 0; $i < $length; ++$i) { + $pieces []= $keyspace[random_int(0, $max)]; + } + + return implode('', $pieces); + } +} diff --git a/forum/qa-plugin/q2a-googleauthenticator-login/src/i18n/q2a-googleauthenticator-lang-default.php b/forum/qa-plugin/q2a-googleauthenticator-login/src/i18n/q2a-googleauthenticator-lang-default.php new file mode 100644 index 00000000..eae5ce67 --- /dev/null +++ b/forum/qa-plugin/q2a-googleauthenticator-login/src/i18n/q2a-googleauthenticator-lang-default.php @@ -0,0 +1,29 @@ + 'Weryfikacja dwuetapowa', + 'plugin_is_enabled' => 'Weryfikacja dwuetapowa jest włączona od ', + 'plugin_is_disabled' => 'Weryfikacja dwuetapowa jest wyłączona', + 'enable_plugin' => 'Włącz', + 'disable_plugin' => 'Wyłącz', + 'enabled_plugin' => 'Jeśli chcesz wyłączyć użytkownikom 2FA, odznacz ten checkbox', + 'disabled_plugin' => 'Włącz użytkownikom 2FA', + + 'default_auth' => '(Domyślna)', + 'confirm' => 'Gotowe', + 'next' => 'Dalej', + 'cancel' => 'Anuluj', + 'generate' => 'Wygeneruj', + + '2fa_setup_error' => 'Nie udało się włączyć autoryzacji dwuetapowej. Zgłoś ten problem administratorowi.', + 'saved_plugin_settings' => 'Konfiguracja została zapisana', + 'save_settings' => 'Zapisz', + 'secret_error' => 'Nieprawidłowy sekret. Spróbuj ponownie lub zgłoś adminowi.', + + 'recover_code_page_info' => 'Zalogowałeś się z wykorzystaniem kodu awaryjnego. Nie jest on już aktywny. Jeśli nie możesz już logować się korzystając z weryfikacji dwuetapowej pamiętaj, aby w ustawieniach wyłączyć logowanie dwuetapowe. Inaczej nie będziesz mógł/mogła się już zalogować.', + '2fa_data_info' => 'Właśnie włączyłeś weryfikację dwuetapową. To dobry krok na drodze do całkowitego zabezpieczenia Twojego konta. Aby skorzystać z weryfikacji dwuetapowej, zeskanuj ten kod QR swoim telefonem {{ QR_CODE }} {{ ERROR_START }}Nie możesz zeskanować kodu QR? Możesz też wpisać ten kod do swojej aplikacji (tzw. secret): {{ SECRET }}{{ ERROR_END }}W razie sytuacji awaryjnej, w miejscu kodu z aplikacji możesz również wpisać ten kod zapasowy: {{ RECOVERY_CODE }} Pamiętaj że jest on wykorzystywany w wyjątkowych sytuacjach i działa tylko na jedno logowanie - później bedziesz musial wygenerować nowy kod.', + 'code_input' => 'Kod weryfikacyjny:', + 'send' => 'Wyślij', + 'invalid_code' => 'Błędny kod' + +]; diff --git a/forum/qa-theme/SnowFlat/qa-custom.css b/forum/qa-theme/SnowFlat/qa-custom.css new file mode 100644 index 00000000..a72110d6 --- /dev/null +++ b/forum/qa-theme/SnowFlat/qa-custom.css @@ -0,0 +1,50 @@ +.qa-custom-center { + text-align: center; + line-height: 1.5em; +} + +.qa-custom-center a { + color: #fff; + border-bottom: 1px dotted #fff; +} +.qa-custom-center a:hover { + color: #fff; + border-bottom: 0; +} + +.qa-custom-center h2 { + text-align: left; + margin: 0; + padding-bottom: 0.83em; +} + +.qa-custom-2fa-text { + padding-top: 0.5em; + /*line-height: 1.5em;*/ + font-size: 1em; + border-top: 1px solid #313640; + border-bottom: 1px solid #313640; +} + +.qa-custom-2fa-text p { + margin: 0; + padding-bottom: 0.5em; +} + +.qa-custom-2fa-qrcode { + text-align: center; + margin-bottom: 1.5em +} + +.qa-custom-2fa-qrcode img { + border-radius: 10px; + border: 1px solid #0f192a; +} + +.qa-custom-2fa-code { + text-align: center; +} + +.qa-custom-2fa-text-bottom { + margin-bottom: 1.5em +} diff --git a/forum/qa-theme/SnowFlat/qa-styles.css b/forum/qa-theme/SnowFlat/qa-styles.css index 06a4f22a..9f03a62b 100644 --- a/forum/qa-theme/SnowFlat/qa-styles.css +++ b/forum/qa-theme/SnowFlat/qa-styles.css @@ -8,6 +8,7 @@ Theme License: GPLv2 */ +@import "./qa-custom.css"; .qa-q-item-clear, .clearfix, .qa-nav-main-list, .qa-nav-sub, .qam-qa-list-meta-box, .qa-q-item-avatar-meta, .qa-page-links, .qa-page-links-list, .qa-template-admin .qa-message-buttons, .qam-approve-users .qa-message-item, .qa-message-item, .qa-part-q-view, .qa-q-view-buttons, .qa-part-form-q-edit, .qa-a-list-item, .qa-a-item-buttons, .qa-c-item-buttons, .qa-c-item-clear, .qam-footer-row, .qa-nav-footer-list, .qa-footer-clear { *zoom: 1;