From 3bb15d6a41cb57cf1a2046e87400beb0f7f3efe4 Mon Sep 17 00:00:00 2001 From: xiaomlove <353856593@qq.com> Date: Thu, 10 Jun 2021 21:07:20 +0800 Subject: [PATCH] Google Authenticator --- app/Console/Commands/AttendanceCleanup.php | 71 +++++++++++++++++++ app/Console/Commands/Test.php | 4 +- app/Models/Attendance.php | 12 ++++ classes/class_attendance.php | 33 +++++++-- composer.json | 1 + composer.lock | 60 +++++++++++++++- ...s_and_total_points_to_attendance_table.php | 36 ++++++++++ ...005_add_two_step_secret_to_users_table.php | 35 +++++++++ include/functions.php | 2 +- include/globalfunctions.php | 31 ++++++-- lang/chs/lang_login.php | 2 + lang/chs/lang_takelogin.php | 2 + lang/chs/lang_usercp.php | 7 +- lang/en/lang_usercp.php | 5 +- public/login.php | 1 + public/takelogin.php | 14 +++- public/usercp.php | 46 ++++++++++++ 17 files changed, 339 insertions(+), 23 deletions(-) create mode 100644 app/Console/Commands/AttendanceCleanup.php create mode 100644 app/Models/Attendance.php create mode 100644 database/migrations/2021_06_10_150523_add_total_days_and_total_points_to_attendance_table.php create mode 100644 database/migrations/2021_06_10_181005_add_two_step_secret_to_users_table.php diff --git a/app/Console/Commands/AttendanceCleanup.php b/app/Console/Commands/AttendanceCleanup.php new file mode 100644 index 00000000..c11a9f7b --- /dev/null +++ b/app/Console/Commands/AttendanceCleanup.php @@ -0,0 +1,71 @@ +groupBy('uid')->selectRaw('uid, max(id) as max_id'); + $page = 1; + $size = 1000; + while (true) { + $rows = $query->forPage($page, $size)->get(); + $log = "sql: " . last_query() . ", count: " . $rows->count(); + do_log($log); + $this->info($log); + if ($rows->isEmpty()) { + $log = "no more data...."; + do_log($log); + $this->info($log); + break; + } + foreach ($rows as $row) { + do { + $deleted = Attendance::query() + ->where('uid', $row->uid) + ->where('id', '<', $row->max_id) + ->limit(10000) + ->delete(); + $log = "delete: $deleted by sql: " . last_query(); + do_log($log); + $this->info($log); + } while ($deleted > 0); + } + $page++; + } + return 0; + } +} diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php index 75659466..415434a2 100644 --- a/app/Console/Commands/Test.php +++ b/app/Console/Commands/Test.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Models\Attendance; use App\Models\Exam; use App\Models\ExamProgress; use App\Models\ExamUser; @@ -52,8 +53,7 @@ class Test extends Command */ public function handle() { - $r = 'https://hdtime.org/download.php?downhash=' . urlencode('1|Roqd'); - dd($r); + } } diff --git a/app/Models/Attendance.php b/app/Models/Attendance.php new file mode 100644 index 00000000..95d09349 --- /dev/null +++ b/app/Models/Attendance.php @@ -0,0 +1,12 @@ + 'datetime', + ]; +} diff --git a/classes/class_attendance.php b/classes/class_attendance.php index 1a724fba..84aa83ba 100644 --- a/classes/class_attendance.php +++ b/classes/class_attendance.php @@ -23,10 +23,16 @@ class Attendance public function attend($initial = 10, $step = 5, $maximum = 2000, $continous = array()) { if($this->check(true)) return false; - $count = get_row_count('attendance', sprintf('WHERE `uid` = %u', $this->userid)); - $points = min($initial + $step * $count, $maximum); - $res = sql_query(sprintf('SELECT DATEDIFF(%s, `added`) AS diff, `days` FROM `attendance` WHERE `uid` = %u ORDER BY `id` DESC LIMIT 1', sqlesc($this->curdate), $this->userid)) or sqlerr(__FILE__,__LINE__); - list($datediff, $days) = mysql_num_rows($res) ? mysql_fetch_row($res) : array('diff' => 0, 'days' => 0); + $res = sql_query(sprintf('SELECT DATEDIFF(%s, `added`) AS diff, `days`, `total_days`, `total_points` FROM `attendance` WHERE `uid` = %u ORDER BY `id` DESC LIMIT 1', sqlesc($this->curdate), $this->userid)) or sqlerr(__FILE__,__LINE__); + $doUpdate = mysql_num_rows($res); + if ($doUpdate) { + $row = mysql_fetch_row($res); + do_log("uid: {$this->userid}, row: " . json_encode($row)); + } else { + $row = [0, 0, 0, 0]; + } + $points = min($initial + $step * $row['total_attend_times'], $maximum); + list($datediff, $days, $totalDays, $totalPoints) = $row; $cdays = $datediff == 1 ? ++$days : 1; if($cdays > 1){ krsort($continous); @@ -37,10 +43,23 @@ class Attendance } } } - sql_query(sprintf('INSERT INTO `attendance` (`uid`,`added`,`points`,`days`) VALUES (%u, %s, %u, %u)', $this->userid, sqlesc(date('Y-m-d H:i:s')), $points, $cdays)) or sqlerr(__FILE__, __LINE__); +// sql_query(sprintf('INSERT INTO `attendance` (`uid`,`added`,`points`,`days`) VALUES (%u, %s, %u, %u)', $this->userid, sqlesc(date('Y-m-d H:i:s')), $points, $cdays)) or sqlerr(__FILE__, __LINE__); + if ($doUpdate) { + $sql = sprintf( + 'UPDATE `attendance` set added = %s, points = %s, days = %s, total_days= %s, total_points = %s where uid = %s order by id desc limit 1', + sqlesc(date('Y-m-d H:i:s')), $points, $cdays, $totalDays + 1, $totalPoints + $points, $this->userid + ); + } else { + $sql = sprintf( + 'INSERT INTO `attendance` (`uid`, `added`, `points`, `days`, `total_days`, `total_points`) VALUES (%u, %s, %u, %u, %u, %u)', + $this->userid, sqlesc(date('Y-m-d H:i:s')), $points, $cdays, $totalDays + 1, $totalPoints + $points + ); + } + do_log(sprintf('uid: %s, date: %s, doUpdate: %s, sql: %s', $this->userid, $this->curdate, $doUpdate, $sql), 'notice'); + sql_query($sql) or sqlerr(__FILE__, __LINE__); KPS('+', $points, $this->userid); global $Cache; $Cache->delete_value($this->cachename); - return array(++$count, $cdays, $points); + return array(++$totalDays, $cdays, $points); } -} \ No newline at end of file +} diff --git a/composer.json b/composer.json index 4a60c3a9..a7a418d6 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "laravel/tinker": "^2.5", "nao-pon/flysystem-google-drive": "^1.1", "orangehill/iseed": "^3.0", + "phpgangsta/googleauthenticator": "dev-master", "rhilip/bencode": "^1.1", "swiftmailer/swiftmailer": "^6.2" }, diff --git a/composer.lock b/composer.lock index 823ee73d..1cfcab00 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a4f805b49bd52cf34ee5f763976ed030", + "content-hash": "fcf04cef5848b9eb1d15b0c297de364f", "packages": [ { "name": "asm89/stack-cors", @@ -3282,6 +3282,60 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "phpgangsta/googleauthenticator", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/PHPGangsta/GoogleAuthenticator.git", + "reference": "505c2af8337b559b33557f37cda38e5f843f3768" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPGangsta/GoogleAuthenticator/zipball/505c2af8337b559b33557f37cda38e5f843f3768", + "reference": "505c2af8337b559b33557f37cda38e5f843f3768", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3" + }, + "default-branch": true, + "type": "library", + "autoload": { + "classmap": [ + "PHPGangsta/GoogleAuthenticator.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-4-Clause" + ], + "authors": [ + { + "name": "Michael Kliewe", + "email": "info@phpgangsta.de", + "homepage": "http://www.phpgangsta.de/", + "role": "Developer" + } + ], + "description": "Google Authenticator 2-factor authentication", + "keywords": [ + "googleauthenticator", + "rfc6238", + "totp" + ], + "support": { + "issues": "https://github.com/PHPGangsta/GoogleAuthenticator/issues", + "source": "https://github.com/PHPGangsta/GoogleAuthenticator" + }, + "time": "2019-03-20T00:55:58+00:00" + }, { "name": "phpoption/phpoption", "version": "1.7.5", @@ -9400,7 +9454,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "phpgangsta/googleauthenticator": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/database/migrations/2021_06_10_150523_add_total_days_and_total_points_to_attendance_table.php b/database/migrations/2021_06_10_150523_add_total_days_and_total_points_to_attendance_table.php new file mode 100644 index 00000000..eef06b57 --- /dev/null +++ b/database/migrations/2021_06_10_150523_add_total_days_and_total_points_to_attendance_table.php @@ -0,0 +1,36 @@ +integer('total_days')->default(0); + $table->integer('total_points')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('attendance', function (Blueprint $table) { + $table->dropColumn(['total_days', 'total_points']); + }); + } +} diff --git a/database/migrations/2021_06_10_181005_add_two_step_secret_to_users_table.php b/database/migrations/2021_06_10_181005_add_two_step_secret_to_users_table.php new file mode 100644 index 00000000..ccf3d8cb --- /dev/null +++ b/database/migrations/2021_06_10_181005_add_two_step_secret_to_users_table.php @@ -0,0 +1,35 @@ +string('two_step_secret')->default(''); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('two_step_secret'); + }); + } +} diff --git a/include/functions.php b/include/functions.php index aad8b560..db249aba 100644 --- a/include/functions.php +++ b/include/functions.php @@ -2476,7 +2476,7 @@ else { = UC_SYSOP) { ?> [] [] []: - %s', $lang_functions['text_attendance']);}?> + %s', $lang_functions['text_attendance']);}?> []:
diff --git a/include/globalfunctions.php b/include/globalfunctions.php index 9ab5e189..8efb2e22 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -544,16 +544,33 @@ function fail(...$args) function last_query($all = false) { - if (IN_NEXUS) { - $queries = \Illuminate\Database\Capsule\Manager::connection(\Nexus\Database\NexusDB::ELOQUENT_CONNECTION_NAME)->getQueryLog(); - } else { - $queries = \Illuminate\Support\Facades\DB::connection(config('database.default'))->getQueryLog(); + static $connection, $pdo; + if (is_null($connection)) { + if (IN_NEXUS) { + $connection = \Illuminate\Database\Capsule\Manager::connection(\Nexus\Database\NexusDB::ELOQUENT_CONNECTION_NAME); + } else { + $connection = \Illuminate\Support\Facades\DB::connection(config('database.default')); + } + $pdo = $connection->getPdo(); + } + $queries = $connection->getQueryLog(); + if (!$all) { + $queries = [last($queries)]; + } + $queryFormatted = []; + foreach ($queries as $query) { + $sqlWithPlaceholders = str_replace(['%', '?'], ['%%', '%s'], $query['query']); + $bindings = $query['bindings']; + $realSql = $sqlWithPlaceholders; + if (count($bindings) > 0) { + $realSql = vsprintf($sqlWithPlaceholders, array_map([$pdo, 'quote'], $bindings)); + } + $queryFormatted[] = $realSql; } if ($all) { - return nexus_json_encode($queries); + return nexus_json_encode($queryFormatted); } - $query = last($queries); - return nexus_json_encode($query); + return $queryFormatted[0]; } function format_datetime($datetime, $format = 'Y-m-d H:i') diff --git a/lang/chs/lang_login.php b/lang/chs/lang_login.php index 4ff579ce..45c5a941 100644 --- a/lang/chs/lang_login.php +++ b/lang/chs/lang_login.php @@ -32,5 +32,7 @@ $lang_login = array 'submit_clear' => "清空", 'text_select_lang' => "Select Site Language: ", 'head_login' => "登录", + 'rowhead_two_step_code' => '两步验证', + 'two_step_code_tooltip' => '如有设置必须填写', ); ?> diff --git a/lang/chs/lang_takelogin.php b/lang/chs/lang_takelogin.php index b6d45f95..862826fe 100644 --- a/lang/chs/lang_takelogin.php +++ b/lang/chs/lang_takelogin.php @@ -6,6 +6,8 @@ $lang_takelogin = array 'std_login_fail' => "登录失败!", 'std_account_disabled' => "该账号已被禁用。", 'std_user_account_unconfirmed' => "该账户还未通过验证。如果你没有收到验证邮件,试试重新发送验证邮件。", + 'std_require_two_step_code' => '需要两步验证 code', + 'std_invalid_two_step_code' => '两步验证 code 无效', ); ?> diff --git a/lang/chs/lang_usercp.php b/lang/chs/lang_usercp.php index 8fa27dff..b7883f75 100644 --- a/lang/chs/lang_usercp.php +++ b/lang/chs/lang_usercp.php @@ -245,7 +245,12 @@ $lang_usercp = array 'text_show_advertisement_note' => "我想看到广告", 'row_promotion_link' => "宣传链接", 'text_update_promotion_link' => "更新宣传链接", - 'text_read_more' => "了解更多" + 'text_read_more' => "了解更多", + 'row_two_step_secret' => '两步验证', + 'text_two_step_secret_bind_by_qrdoe_note' => '你可以使用 Google Authenticator 或 Authy 扫描左侧二维码

如果左侧二维码没有加载成功,可以尝试打开此链接进行加载:', + 'text_two_step_secret_bind_manually_note' => '或者在 APP 中手动输入以下 Key:', + 'text_two_step_secret_bind_complete_note' => '输入 code 完成两步验证', + 'text_two_step_secret_unbind_note' => '输入 code 取消两步验证', ); ?> diff --git a/lang/en/lang_usercp.php b/lang/en/lang_usercp.php index e69d75d5..abc25b87 100644 --- a/lang/en/lang_usercp.php +++ b/lang/en/lang_usercp.php @@ -245,7 +245,10 @@ $lang_usercp = array 'text_show_advertisement_note' => "I'd like to see the advertisements", 'row_promotion_link' => "Promotion Link", 'text_update_promotion_link' => "Update Promotion Link", - 'text_read_more' => "Read More" + 'text_read_more' => "Read More", + 'row_two_step_secret' => 'Two step authentication', + 'text_two_step_secret_bind_note' => '', + 'text_two_step_secret_remove_note' => '', ); ?> diff --git a/public/login.php b/public/login.php index c202262d..e1620f69 100644 --- a/public/login.php +++ b/public/login.php @@ -53,6 +53,7 @@ if (!empty($_GET["returnto"])) { +verifyCode($row['two_step_secret'], $_POST['two_step_code'])) { + failedlogins($lang_takelogin['std_invalid_two_step_code']); + } +} + if ($row["passhash"] != md5($row["secret"] . $password . $row["secret"])) login_failedlogins(); @@ -64,7 +74,7 @@ if (isset($_POST["logout"]) && $_POST["logout"] == "yes") logincookie($row["id"], $passh,1,900,$securelogin_indentity_cookie, $ssl, $trackerssl); //sessioncookie($row["id"], $passh,true); } -else +else { logincookie($row["id"], $passh,1,0x7fffffff,$securelogin_indentity_cookie, $ssl, $trackerssl); //sessioncookie($row["id"], $passh,false); diff --git a/public/usercp.php b/public/usercp.php index 7c67a575..76b88ac5 100644 --- a/public/usercp.php +++ b/public/usercp.php @@ -677,6 +677,25 @@ tr_small($lang_usercp['row_funbox'],"verifyCode($secretToVerify, $twoStepSecretHash)) { + stderr($lang_usercp['std_error'], 'Invalid two step code'.goback("-2"), 0); + die; + } + } if ($chpassword != "") { if ($chpassword == $CURUSER["username"]) { @@ -809,6 +828,8 @@ EOD; $chpassword = $_POST["chpassword"]; $passagain = $_POST["passagain"]; $privacy = $_POST["privacy"]; + $two_step_secret = $_POST["two_step_secret"] ?? ''; + $two_step_code = $_POST["two_step_code"]; if ($resetpasskey == 1) print(""); if ($resetauthkey == 1) @@ -817,6 +838,8 @@ EOD; print(""); print(""); print(""); + print(""); + print(""); Print("\n"); submit(); print("
".$lang_usercp['row_security_check']."
".$lang_usercp['text_security_check_note']."
"); @@ -828,6 +851,29 @@ EOD; form ("security"); tr_small($lang_usercp['row_reset_passkey'],"".$lang_usercp['checkbox_reset_my_passkey']."
".$lang_usercp['text_reset_passkey_note']."", 1); tr_small($lang_usercp['row_reset_authkey'],"".$lang_usercp['checkbox_reset_my_authkey']."
".$lang_usercp['text_reset_authkey_note']."", 1); + + //two step authentication + if (!empty($CURUSER['two_step_secret'])) { + tr_small($lang_usercp['row_two_step_secret'],"".$lang_usercp['text_two_step_secret_unbind_note'], 1); + } else { + $ga = new \PHPGangsta_GoogleAuthenticator(); + $twoStepSecret = $ga->createSecret(); + $twoStepQrCodeUrl = $ga->getQRCodeGoogleUrl(sprintf('%s(%s)', get_setting('basic.SITENAME'), $CURUSER['username']), $twoStepSecret); + $twoStepY = '
'; + $twoStepY .= sprintf('
', $twoStepQrCodeUrl); + $twoStepY .= sprintf( + '
%sLink

%s%s

%s
', + $lang_usercp['text_two_step_secret_bind_by_qrdoe_note'], + $twoStepQrCodeUrl, + $lang_usercp['text_two_step_secret_bind_manually_note'], + $twoStepSecret, + $lang_usercp['text_two_step_secret_bind_complete_note'], + $twoStepSecret + ); + $twoStepY .= '
'; + tr_small($lang_usercp['row_two_step_secret'], $twoStepY, 1); + } + if ($disableemailchange != 'no' && $smtptype != 'none') //system-wide setting tr_small($lang_usercp['row_email_address'], "
".$lang_usercp['text_email_address_note']."", 1); tr_small($lang_usercp['row_change_password'], "", 1);