-
Notifications
You must be signed in to change notification settings - Fork 8
2fa #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
2fa #204
Changes from 1 commit
e268225
07a8d1c
b4b00ae
fa5d345
e5ffee8
807281f
ea5bf96
3be7021
0f28fea
8868f1e
8077ebd
ddb2bba
8f208bf
9470410
147984e
2c8ad81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "event15/q2a-googleauthenticator-login", | ||
| "type": "library", | ||
| "license": "GPLv2", | ||
| "authors": [ | ||
| { | ||
| "name": "Marek Woś", | ||
| "email": "[email protected]" | ||
| } | ||
| ], | ||
| "minimum-stability": "dev", | ||
| "autoload": { | ||
| "psr-4": { | ||
| "CodersCommunity\\": "src/" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. myślę, że namespace powinien być bardziej konkretny, tzn. zawierać jeszcze chociażby nazwę plugina, ale tu jest cała kwestia uzywania composera, opisałem w komentarzu
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. miałem na caly ten kod zupełnie inny pomysł z początku, ale zostałem uprzedzony i kod z obiektowego stał się q2a'owy. Przy takim stanie rzeczy przyjąłem to co już było i nie starałem się diametralnie wszystkiego zmieniać. Jeśli się uda dodać do composera głównego zależność a jej tu nie budować, to być może to zniknie, z resztą o ile mam dobrą pamięć, to ten namespace jest nieużyty, a na pewno nie tak, jak powinien.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nawet jeśli ten byłby nie użyty to CodersCommunity kojarzy się z nazwą całej organizacji forumowej, więc czemu ma należeć do jednego plugina. Tym bardziej już gdyby coś było w globalnym lepiej byłoby dać chociażby |
||
| } | ||
| }, | ||
| "require": { | ||
| "robthree/twofactorauth": "dev-master" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nie lepiej dla bezpieczeństwa wskazać konkretną wersję o jaką nam chodzi?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK |
||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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ś", | ||
event15 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "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" | ||
| } | ||
event15 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| <?php | ||
|
|
||
| require_once GOOGLEAUTHENTICATOR_BASIC_PATH . '/src/Init.php'; | ||
|
|
||
| class qa_html_theme_layer extends qa_html_theme_base | ||
| { | ||
| public function doctype() | ||
| { | ||
| parent::doctype(); | ||
|
|
||
| if ('account' === $this->request && true === (bool) qa_opt('googleauthenticator_login')) { | ||
| $content = [ | ||
| 'tags' => 'method="post" action="' . qa_self_html() . '"', | ||
| 'style' => 'wide', | ||
| 'title' => qa_lang_html('plugin_2fa/title') | ||
| ]; | ||
|
|
||
| $content += $this->enablePlugin(); | ||
|
|
||
| $this->content['form_2fa'] = $content; | ||
| qa_html_theme_base::doctype(); | ||
| } | ||
| } | ||
|
|
||
| private function getUserQuery($userId) | ||
| { | ||
| // Return the user with the specified userid (should return one user or null) | ||
| $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 | ||
| ) | ||
| ); | ||
|
|
||
| return empty($users) ? null : $users[0]; | ||
| } | ||
|
|
||
| /** | ||
| * This method enables 2fa on selected user. | ||
| * | ||
| * @param $userId | ||
| * @param $isEnabled | ||
| * @param $secret | ||
| * @param $recoveryCode | ||
| * | ||
| * @return mixed | ||
| */ | ||
| private function updateUserEnable2FA($userId, $isEnabled, $secret = null, $recoveryCode = null) | ||
event15 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| $time = new DateTime('now'); | ||
| $formatter = new IntlDateFormatter('pl_PL', IntlDateFormatter::SHORT, IntlDateFormatter::SHORT); | ||
| $formatter->setPattern('EEEE, dd MMMM yyyy, HH:mm:ss'); | ||
event15 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| $result = qa_db_query_sub( | ||
| 'UPDATE ^users SET 2fa_enabled=#, 2fa_change_date=$, 2fa_secret=$, 2fa_recovery_code=$ WHERE userid=#', | ||
| $isEnabled, | ||
| $formatter->format($time), | ||
| $secret, | ||
| $recoveryCode, | ||
| $userId | ||
| ); | ||
|
|
||
| if (true === $result) { | ||
| return $isEnabled; | ||
| } | ||
|
|
||
| user_error('Nie udało się włączyć autoryzacji dwuetapowej. Zgłoś ten problem administratorowi.'); | ||
| } | ||
|
|
||
| private function enablePlugin() | ||
| { | ||
| $userAccount = $this->getUser(); | ||
| $userActive2fa = $userAccount['2fa_enabled']; | ||
|
|
||
| if (qa_clicked('doenable2fa')) { | ||
| $this->init = new Init(); | ||
| $this->init->createSecret(); | ||
| $recoveryCode = $this->init->getRandomRecoveryCode(); | ||
| $secret = $this->init->getSecret(); | ||
|
|
||
| $userActive2fa = $this->updateUserEnable2FA($userAccount['userid'], true, $secret, $recoveryCode); | ||
| } elseif (qa_clicked('dodisable2fa')) { | ||
| $userActive2fa = $this->updateUserEnable2FA($userAccount['userid'], false); | ||
| } | ||
|
|
||
| $userAccount = $this->getUser(); | ||
| if (true === (bool) $userActive2fa) { | ||
|
|
||
| $result = $this->render2FAEnabledForm($userAccount['2fa_change_date']); | ||
|
|
||
| if (isset($this->init)) { | ||
| $note = qa_lang_html('plugin_2fa/2fa_data_info'); | ||
| $note = str_replace('{{ QR_CODE }}', '<br><center><img src="' . $this->init->getQRCode() . '"></center><br>', $note); | ||
| $note = str_replace('{{ SECRET }}', '<code>' . $secret . '</code>', $note); | ||
| $note = str_replace('{{ RECOVERY_CODE }}', '<code>' . $recoveryCode . '</code>', $note); | ||
| $note = str_replace('{{ ERROR_START }}', '<br><div class="qa-error">', $note); | ||
| $note = str_replace('{{ ERROR_END }}', '</div><br>', $note); | ||
|
|
||
| $result['fields'][] = [ | ||
| 'style' => 'tall', | ||
| 'type' => 'static', | ||
| 'note' => $note | ||
| ]; | ||
| } | ||
|
|
||
| } else { | ||
| $result = $this->render2FADisabledForm($userAccount['2fa_change_date']); | ||
| } | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| private function render2FAEnabledForm($date) | ||
| { | ||
| return [ | ||
| 'fields' => [ | ||
| 'old' => [ | ||
| 'label' => qa_lang_html('plugin_2fa/plugin_is_enabled'), | ||
| 'tags' => 'name="oldpassword" disabled', | ||
| 'value' => $date, | ||
| 'type' => 'input' | ||
| ], | ||
| ], | ||
| 'buttons' => [ | ||
| 'enable' => [ | ||
| 'label' => qa_lang_html('plugin_2fa/disable_plugin') | ||
| ] | ||
| ], | ||
| 'hidden' => [ | ||
| 'dodisable2fa' => '1', | ||
| 'code' => qa_get_form_security_code('2faform') | ||
| ] | ||
| ]; | ||
| } | ||
|
|
||
| private function render2FADisabledForm($date) | ||
| { | ||
| return [ | ||
| 'fields' => [ | ||
| 'old' => [ | ||
| 'label' => qa_lang_html('plugin_2fa/plugin_is_disabled'), | ||
| 'value' => '', | ||
| 'type' => 'static' | ||
| ] | ||
| ], | ||
| 'buttons' => [ | ||
| 'enable_2fa' => [ | ||
| 'label' => qa_lang_html('plugin_2fa/enable_plugin') | ||
| ] | ||
| ], | ||
| 'hidden' => [ | ||
| 'doenable2fa' => '1', | ||
| 'code' => qa_get_form_security_code('2faform') | ||
| ] | ||
| ]; | ||
| } | ||
|
|
||
| /** | ||
| * @return null | ||
| */ | ||
| private function getUser() | ||
| { | ||
| $userId = qa_get_logged_in_userid(); | ||
|
|
||
| if (!isset($userId)) { | ||
| qa_redirect('login'); | ||
| } | ||
|
|
||
| $userAccount = $this->getUserQuery($userId); | ||
|
|
||
| return $userAccount; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| <?php | ||
|
|
||
| function qa_set_logged_in_user($userId, $handle = '', $remember = false, $source = null) | ||
| { | ||
| require_once QA_INCLUDE_DIR . 'app/cookies.php'; | ||
|
|
||
| qa_start_session(); | ||
|
|
||
| if (!isset($userId)) { | ||
| // logout | ||
|
|
||
| qa_report_event( | ||
| 'u_logout', | ||
| qa_get_logged_in_userid(), | ||
| qa_get_logged_in_handle(), | ||
| qa_cookie_get() | ||
| ); | ||
|
|
||
| qa_clear_session_cookie(); | ||
| qa_clear_session_user(); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| // login | ||
|
|
||
| require_once QA_INCLUDE_DIR . 'db/selects.php'; | ||
|
|
||
| $result = qa_db_read_all_assoc(qa_db_query_sub( | ||
| 'SELECT 2fa_enabled FROM ^users WHERE userid = #', | ||
| $userId | ||
| )); | ||
|
|
||
| if (count($result) != 1) { | ||
| echo 'Invalid num_rows'; | ||
| die; | ||
| } | ||
|
|
||
| $usingTwoFactorAuth = (bool) $result[0]['2fa_enabled']; | ||
|
|
||
| if ($usingTwoFactorAuth && '2fa' !== $source) { | ||
| echo ' | ||
| <form method="post" id="form" action="./2fa-auth"> | ||
| <input name="login" type="hidden" value="' . qa_post_text('emailhandle') . '"> | ||
| <input name="password" type="hidden" value="' . qa_post_text('password') . '"> | ||
| <input name="remember" type="hidden" value="' . qa_post_text('remember') . '"> | ||
| <input name="redirect" type="hidden" value="' . $_GET['to'] . '"> | ||
| </form> | ||
| Trwa przekierowanie... | ||
| <script> | ||
| document.getElementById("form").submit(); | ||
| </script> | ||
| '; | ||
| exit; | ||
| } | ||
|
|
||
| $userInfo = qa_db_single_select( | ||
| qa_db_user_account_selectspec( | ||
| $userId, | ||
| true | ||
| ) | ||
| ); | ||
|
|
||
| if (empty($userInfo['sessioncode']) || $source !== $userInfo['sessionsource'] && '2fa' !== $source) { | ||
| $sessionCode = qa_db_user_rand_sessioncode(); | ||
|
|
||
| qa_db_user_set( | ||
| $userId, | ||
| [ | ||
| 'sessioncode' => $sessionCode, | ||
| 'sessionsource' => $sessionSource, | ||
| ] | ||
| ); | ||
| } else { | ||
| $sessionCode = $userInfo['sessioncode']; | ||
| } | ||
|
|
||
| qa_db_user_logged_in($userId, qa_remote_ip_address()); | ||
| qa_set_session_cookie($handle, $sessionCode, $remember); | ||
|
|
||
| qa_report_event('u_login', $userId, $userInfo['handle'], qa_cookie_get()); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. brak entera |
||
Uh oh!
There was an error while loading. Please reload this page.