From 5387bdb4b9cf97db571afedb3429d084bff5a40f Mon Sep 17 00:00:00 2001 From: leo <342377760@qq.com> Date: Thu, 4 Mar 2021 13:19:16 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=20=E8=B0=B7=E6=AD=8C=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/admin/controller/Base.php | 71 ++++++ application/admin/controller/Index.php | 5 +- application/admin/controller/Login.php | 79 +++++- application/admin/view/index.html | 121 +++++++++ application/admin/view/login/index.html | 110 +++++++- application/config.php | 9 +- composer.json | 2 +- extend/PHPGangsta/GoogleAuthenticator.php | 297 ++++++++++++++++++++++ public/.htaccess | 13 +- public/static/admin/js/qrcode.min.js | 1 + sql/2021-03-03.sql | 8 + 11 files changed, 699 insertions(+), 17 deletions(-) create mode 100644 extend/PHPGangsta/GoogleAuthenticator.php create mode 100644 public/static/admin/js/qrcode.min.js create mode 100644 sql/2021-03-03.sql diff --git a/application/admin/controller/Base.php b/application/admin/controller/Base.php index ba09c9e..6184295 100644 --- a/application/admin/controller/Base.php +++ b/application/admin/controller/Base.php @@ -5,6 +5,7 @@ */ namespace app\admin\controller; +use PHPGangsta\GoogleAuthenticator; use think\Controller; use Repository\IpRepository; @@ -17,6 +18,12 @@ class Base extends Controller public $role_name = ''; public $admin_id = ''; public $group_name = ''; + /** + * google 二次验证码长度 + * @var int + */ + protected $googleAuthSecretLength = 64; + public function _initialize() { // $ipAccess = (new IpRepository)->ipAccess(); @@ -216,4 +223,68 @@ class Base extends Controller { return $table.'_'.($uid % config('chat_table_num')); } + + // 创建二次验证秘钥 + public function make_google_auth_secret () + { + $ga = new GoogleAuthenticator(); + $key = $ga->createSecret($this->googleAuthSecretLength); + $content = $ga->getQrContent($this->request->host(),$key,session('user_name').'['.date('Y-m-d H:i:s').']'); + if (isset($key)){ + return json(['code' => 1, 'key' => $key, 'qrcode_url' => $content, 'msg' => '获取成功']); + } + + return json(['code' => 0, 'key' => null, 'qrcode_url' => null, 'msg' => '获取失败']); + } + + // 谷歌验证 + public function bind_google_auth () + { + if (request()->isPost()) { + $param = input('post.'); + + if (empty($param['new_google_auth'])) { + return json(['code' => -2, 'data' => '', 'msg' => '请输入验证码']); + } + + if (empty($param['key'])) { + return json(['code' => -2, 'data' => '', 'msg' => '请重试']); + } + $old = isset($param['old_google_auth']) ? $param['old_google_auth'] : null; + $code = isset($param['new_google_auth']) ? $param['new_google_auth'] : null; + $secret = $param['key']; + + $google_secret = null; + $admin_id = session('user_id'); + if ($admin_id) { + $google_secret = db('admins')->where(['id' => session('user_id')])->value('google_secret'); + } + if ($google_secret && strlen($google_secret) == $this->googleAuthSecretLength) { + if (empty($param['old_google_auth'])) { + return json(['code' => -2, 'data' => '', 'msg' => '请输入旧验证码']); + } + //先验证老的 + $ga = new GoogleAuthenticator(); + if(!$ga->verifyCode($google_secret, strval($old))){ + return json(['code' => -2, 'data' => '', 'msg' => '旧验证码验证失败']); + } + if (!$ga->verifyCode($secret,$code)){ + return json(['code' => -2, 'data' => '', 'msg' => '验证码验证失败']); + } + //验证新的 + if (db('admins')->where(['id' => session('user_id')])->update(['google_secret' => $secret])){ + return json(['code' => 1, 'data' => '', 'msg' => '绑定成功']); + } + } else { + $ga = new GoogleAuthenticator(); + if (!$ga->verifyCode($secret,$code)){ + return json(['code' => -2, 'data' => '', 'msg' => '验证码验证失败']); + } + //验证新的 + if (db('admins')->where(['id' => session('user_id')])->update(['google_secret' => $secret])){ + return json(['code' => 1, 'data' => '', 'msg' => '绑定成功']); + } + } + } + } } diff --git a/application/admin/controller/Index.php b/application/admin/controller/Index.php index e1d952f..0c7ea82 100644 --- a/application/admin/controller/Index.php +++ b/application/admin/controller/Index.php @@ -14,7 +14,7 @@ class Index extends Base { // 获取管理员相关数据信息 $uid = session('user_id'); - $admin_data = db('admins')->field('user_avatar')->where('id', $uid)->find(); + $admin_data = db('admins')->field('user_avatar, google_secret')->where('id', $uid)->find(); // 获取管理员的权限 $menu_list = $this->getAdminMeunList(); $pay_money = 0; @@ -33,6 +33,9 @@ class Index extends Base 'menu_two' => $menu_list['menu_two'], 'role_name' => session('role_name'), 'pay_money' => $pay_money, + 'ga_android' => config('ga_android'), + 'ga_ios' => config('ga_ios'), + 'google_secret' => $admin_data['google_secret'] ? 1 : 0, ]); return $this->fetch('/index'); diff --git a/application/admin/controller/Login.php b/application/admin/controller/Login.php index 32c1de2..bb1dcd4 100644 --- a/application/admin/controller/Login.php +++ b/application/admin/controller/Login.php @@ -6,12 +6,24 @@ namespace app\admin\controller; +use PHPGangsta\GoogleAuthenticator; use think\Controller; use Repository\LogRepository; use Repository\IpRepository; class Login extends Controller { + /** + * google 二次验证码长度 + * @var int + */ + protected $googleAuthSecretLength = 64; + /** + * google 二次验证码超时时间 + * @var int + */ + protected $googleAuthTimeout = 300; + // 登录首页 public function index() { @@ -21,6 +33,8 @@ class Login extends Controller // } $this->assign([ 'version' => config('version'), + 'ga_android' => config('ga_android'), + 'ga_ios' => config('ga_ios'), ]); return $this->fetch(); @@ -42,6 +56,16 @@ class Login extends Controller if (empty($userInfo) || !password_verify($password, $userInfo['password']) || 1 != $userInfo['status']) { return json(['code' => -4, 'data' => '', 'msg' => '密码错误']); } + $token = null; + if (isset($userInfo['google_secret']) && strlen($userInfo['google_secret']) == $this->googleAuthSecretLength) { + $token = md5(time().$userInfo['id']); + $redis = new \Redis(); + $redis->connect(config('cache.host'),config('cache.port')); + $redis->auth(config('cache.password')); + $info = ['user_id'=>$userInfo['id'], 'user_name'=>$userInfo['user_name']]; + $redis->set($token, json_encode($info), $this->googleAuthTimeout); + return json(['code' => 1, 'token' => $token, 'msg' => '请输入谷歌验证码']); + } // 记录管理员状态 session('user_name', $userName); @@ -64,7 +88,7 @@ class Login extends Controller ]; db('admins')->where('id', $userInfo['id'])->update($param); LogRepository::write('系统管理', '登录成功'); - return json(['code' => 1, 'data' => url('index/index'), 'msg' => '登录成功']); + return json(['code' => 1, 'data' => url('index/index'), 'token' => $token, 'msg' => '登录成功']); } } @@ -77,4 +101,57 @@ class Login extends Controller $this->redirect(url('login/index')); } + + public function google_auth () + { + if (request()->isPost()) { + $google_auth = input('param.google_auth'); + $token = input('param.token'); + + if (empty($google_auth)) { + return json(['code' => -1, 'data' => '', 'msg' => '谷歌验证码不能为空']); + } + + if (empty($token)) { + return json(['code' => -1, 'data' => '', 'msg' => '参数错误']); + } + + $redis = new \Redis(); + $redis->connect(config('cache.host'),config('cache.port')); + $redis->auth(config('cache.password')); + $userInfo = $redis->get($token); + if ($userInfo) { + $userInfo = json_decode($userInfo, true); + $ga = new GoogleAuthenticator(); + $google_secret = db('admins')->where('id', $userInfo['user_id'])->value('google_secret'); + if($ga->verifyCode($google_secret, $google_auth)){ + // 记录管理员状态 + session('user_name', $userInfo['user_name']); + session('user_id', $userInfo['user_id']); + session('user_last_login', time()); + // 管理员角色 + $role = db('admin_role') + ->alias('a') + ->where('admin_id', $userInfo['user_id']) + ->join('role r',"r.id=a.role_id") + ->field('name') + ->find(); + $role_name = $role['name'] ? $role['name'] : '暂无角色'; + session('role_name', $role_name); + + // 更新管理员状态 + $param = [ + 'last_login_ip' => request()->ip(), + 'last_login_time' => time(), + ]; + db('admins')->where('id', $userInfo['user_id'])->update($param); + LogRepository::write('系统管理', '校验成功'); + return json(['code' => 1, 'data' => url('index/index'), 'token' => $token, 'msg' => '校验成功']); + } + } + + return json(['code' => -1, 'data' => '', 'msg' => '校验失败']); + } + + } } diff --git a/application/admin/view/index.html b/application/admin/view/index.html index 3e0a8b4..c4a3e28 100644 --- a/application/admin/view/index.html +++ b/application/admin/view/index.html @@ -42,6 +42,9 @@
  • 修改密码
  • +
  • + 谷歌验证 +
  • {if condition="$is_root neq true"}
  • 充值余额 @@ -278,6 +281,48 @@ + + + + + @@ -288,6 +333,7 @@ + + diff --git a/application/admin/view/login/index.html b/application/admin/view/login/index.html index e512d22..79b5b55 100644 --- a/application/admin/view/login/index.html +++ b/application/admin/view/login/index.html @@ -6,8 +6,11 @@ + + + @@ -36,11 +39,38 @@
    + + + + + + \ No newline at end of file diff --git a/application/config.php b/application/config.php index 7233bd7..a9a4ad5 100644 --- a/application/config.php +++ b/application/config.php @@ -28,13 +28,13 @@ return [ // 应用命名空间 'app_namespace' => 'app', // 应用调试模式 - 'app_debug' => false, + 'app_debug' => true, // 应用Trace 'app_trace' => false, // 'default_filter' => 'htmlspecialchars', // 图片读取路径前缀 - 'img_take_prefix' => 'https://' . $_SERVER['HTTP_HOST'], + 'img_take_prefix' => 'http://' . $_SERVER['HTTP_HOST'], // 'img_take_prefix' => 'https://xxkefu.oss-cn-hongkong.aliyuncs.com', 'app_id' => '5a7553560c9cf51c105678f3', 'game_backstage_host' => '192.168.106.10', @@ -58,9 +58,12 @@ return [ 'type' => 'redis', 'host' => '127.0.0.1', 'port' => '6379',//你redis的端口号,可以在配置文件设置其他的 - 'password' => 'Y6HGDReirPceUFwG', //这里是你redis配置的密码,如果没有则留空 + 'password' => 'dbmis_redis', //这里是你redis配置的密码,如果没有则留空 'timeout' => 3600 //缓存时间 ], 'chat_table_num' => 100, + // 谷歌验证下载地址 + 'ga_android' => 'https://www.wandoujia.com/apps/32913', + 'ga_ios' => 'https://apps.apple.com/cn/app/google-authenticator/id388497605', ]; diff --git a/composer.json b/composer.json index 601068e..0ba08ea 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ } }, "require": { - "php": ">=5.4.0", + "php": ">=5.6", "topthink/framework": "~5.0.0", "zoujingli/ip2region": "^1.0", "firebase/php-jwt": "^5.0", diff --git a/extend/PHPGangsta/GoogleAuthenticator.php b/extend/PHPGangsta/GoogleAuthenticator.php new file mode 100644 index 0000000..d4f20cb --- /dev/null +++ b/extend/PHPGangsta/GoogleAuthenticator.php @@ -0,0 +1,297 @@ +createSecret(); + * echo "Secret is: ".$secret."\n\n"; + * + * $qrCodeUrl = $ga->getQRCodeGoogleUrl('Blog', $secret); + * echo "Google Charts URL for the QR-Code: ".$qrCodeUrl."\n\n"; + * + * $oneCode = $ga->getCode($secret); + * echo "Checking Code '$oneCode' and Secret '$secret':\n"; + * + * $checkResult = $ga->verifyCode($secret, $oneCode, 2); // 2 = 2*30sec clock tolerance + * if ($checkResult) { + * echo 'OK'; + * } else { + * echo 'FAILED'; + * } + *Secret is: OQB6ZZGYHCPSX4AK + * + * Google Charts URL for the QR-Code: https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/infoATphpgangsta.de%3Fsecret%3DOQB6ZZGYHCPSX4AK + * + * Checking Code '848634' and Secret 'OQB6ZZGYHCPSX4AK': + * OK + * + * + * @author Michael Kliewe + * @copyright 2012 Michael Kliewe + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * + * @link http://www.phpgangsta.de/ + */ +class GoogleAuthenticator +{ + protected $_codeLength = 6; + + /** + * Create new secret. + * 16 characters, randomly chosen from the allowed base32 characters. + * + * @param int $secretLength + * + * @return string + * @throws \Exception + */ + public function createSecret($secretLength = 16) + { + $validChars = $this->_getBase32LookupTable(); + + // Valid secret lengths are 80 to 640 bits + if ($secretLength < 16 || $secretLength > 128) { + throw new Exception('Bad secret length'); + } + $secret = ''; + $rnd = false; + if (function_exists('random_bytes')) { + $rnd = random_bytes($secretLength); + } elseif (function_exists('mcrypt_create_iv')) { + $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong); + if (!$cryptoStrong) { + $rnd = false; + } + } + if ($rnd !== false) { + for ($i = 0; $i < $secretLength; ++$i) { + $secret .= $validChars[ord($rnd[$i]) & 31]; + } + } else { + throw new Exception('No source of secure random'); + } + + return $secret; + } + + /** + * Calculate the code, with given secret and point in time. + * + * @param string $secret + * @param int|null $timeSlice + * + * @return string + */ + public function getCode($secret, $timeSlice = null) + { + if ($timeSlice === null) { + $timeSlice = floor(time() / 30); + } + + $secretkey = $this->_base32Decode($secret); + + // Pack time into binary string + $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + // Hash it with users secret key + $hm = hash_hmac('SHA1', $time, $secretkey, true); + // Use last nipple of result as index/offset + $offset = ord(substr($hm, -1)) & 0x0F; + // grab 4 bytes of the result + $hashpart = substr($hm, $offset, 4); + + // Unpak binary value + $value = unpack('N', $hashpart); + $value = $value[1]; + // Only 32 bits + $value = $value & 0x7FFFFFFF; + + $modulo = pow(10, $this->_codeLength); + + return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); + } + + /** + * Get QR-Code URL for image, from google charts. + * + * @param string $name + * @param string $secret + * @param string $title + * @param array $params + * + * @return string + */ + public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = array()) + { + $width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200; + $height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200; + $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M'; + + $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); + if (isset($title)) { + $urlencoded .= urlencode('&issuer='.urlencode($title)); + } + + return 'https://chart.googleapis.com/chart?chs='.$width.'x'.$height.'&chld='.$level.'|0&cht=qr&chl='.$urlencoded.''; + } + + /** + * 获取绑定二维码的信息 + * @param $name + * @param $secret + * @param $title + * @return string + */ + public function getQrContent($name, $secret,$title){ + $urlencoded = 'otpauth://totp/'.$name.'?secret='.urlencode($secret); + if (isset($title)) { + $urlencoded .= '&issuer='.urlencode($title); + } + return $urlencoded; + } + + /** + * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now. + * + * @param string $secret + * @param string $code + * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) + * @param int|null $currentTimeSlice time slice if we want use other that time() + * + * @return bool + */ + public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) + { + if ($currentTimeSlice === null) { + $currentTimeSlice = floor(time() / 30); + } + + if (strlen($code) != 6) { + return false; + } + + for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { + $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); + if ($this->timingSafeEquals($calculatedCode, $code)) { + return true; + } + } + + return false; + } + + /** + * Set the code length, should be >=6. + * + * @param int $length + * + * @return PHPGangsta_GoogleAuthenticator + */ + public function setCodeLength($length) + { + $this->_codeLength = $length; + + return $this; + } + + /** + * Helper class to decode base32. + * + * @param $secret + * + * @return bool|string + */ + protected function _base32Decode($secret) + { + if (empty($secret)) { + return ''; + } + + $base32chars = $this->_getBase32LookupTable(); + $base32charsFlipped = array_flip($base32chars); + + $paddingCharCount = substr_count($secret, $base32chars[32]); + $allowedValues = array(6, 4, 3, 1, 0); + if (!in_array($paddingCharCount, $allowedValues)) { + return false; + } + for ($i = 0; $i < 4; ++$i) { + if ($paddingCharCount == $allowedValues[$i] && + substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) { + return false; + } + } + $secret = str_replace('=', '', $secret); + $secret = str_split($secret); + $binaryString = ''; + for ($i = 0; $i < count($secret); $i = $i + 8) { + $x = ''; + if (!in_array($secret[$i], $base32chars)) { + return false; + } + for ($j = 0; $j < 8; ++$j) { + $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); + } + $eightBits = str_split($x, 8); + for ($z = 0; $z < count($eightBits); ++$z) { + $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : ''; + } + } + + return $binaryString; + } + + /** + * Get array with all 32 characters for decoding from/encoding to base32. + * + * @return array + */ + protected function _getBase32LookupTable() + { + return array( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 + 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 + '=', // padding char + ); + } + + /** + * A timing safe equals comparison + * more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html. + * + * @param string $safeString The internal (safe) value to be checked + * @param string $userString The user submitted (unsafe) value + * + * @return bool True if the two strings are identical + */ + private function timingSafeEquals($safeString, $userString) + { + if (function_exists('hash_equals')) { + return hash_equals($safeString, $userString); + } + $safeLen = strlen($safeString); + $userLen = strlen($userString); + + if ($userLen != $safeLen) { + return false; + } + + $result = 0; + + for ($i = 0; $i < $userLen; ++$i) { + $result |= (ord($safeString[$i]) ^ ord($userString[$i])); + } + + // They are only identical strings if $result is exactly 0... + return $result === 0; + } +} diff --git a/public/.htaccess b/public/.htaccess index fe29386..1c41061 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,7 +1,8 @@ - RewriteEngine on - RewriteBase / - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L] - \ No newline at end of file + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php?$1 [QSA,PT,L] + diff --git a/public/static/admin/js/qrcode.min.js b/public/static/admin/js/qrcode.min.js new file mode 100644 index 0000000..993e88f --- /dev/null +++ b/public/static/admin/js/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
    "),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/sql/2021-03-03.sql b/sql/2021-03-03.sql new file mode 100644 index 0000000..ac29350 --- /dev/null +++ b/sql/2021-03-03.sql @@ -0,0 +1,8 @@ +SET FOREIGN_KEY_CHECKS=0; + +ALTER TABLE `ws_admins` +ADD COLUMN `google_secret` char(64) NULL DEFAULT NULL COMMENT '谷歌验证码'; +ALTER TABLE `ws_users` +ADD COLUMN `google_secret` char(64) NULL DEFAULT NULL COMMENT '谷歌验证码'; + +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file