管理员后台 增加 谷歌验证

This commit is contained in:
leo 2021-03-04 13:19:16 +08:00
parent 334733f932
commit 5387bdb4b9
11 changed files with 699 additions and 17 deletions

View File

@ -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' => '绑定成功']);
}
}
}
}
}

View File

@ -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');

View File

@ -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' => '校验失败']);
}
}
}

View File

@ -42,6 +42,9 @@
<li>
<a href="javascript:change()">修改密码</a>
</li>
<li>
<a href="javascript:google_auth()">谷歌验证</a>
</li>
{if condition="$is_root neq true"}
<li>
<a href="javascript:balance({$pay_money})">充值余额</a>
@ -278,6 +281,48 @@
</div>
<!-- 修改个人信息Password -->
<!-- 绑定谷歌验证 -->
<div class="ibox-content" id="google_auth_box" style="display: none">
<form class="form-horizontal m-t" method="post" action="{:url('index/bind_google_auth')}" id="bindForm">
<div id="google_auth_qrcode">
</div>
<p class="p_big p_normal">
<a href="javascript:void 0" onclick="get_google_auth()">刷新二维码</a>
</p>
<p class="p_normal">请使用Google验证器扫描该二维码进行绑定</p>
<p class="p_big p_normal">谷歌验证器下载地址:
<a target="_blank" href="{$ga_android}">安卓版</a>
<a target="_blank" href="{$ga_ios}">苹果版</a>
</p>
{if $google_secret}
<div class="form-group">
<label class="col-sm-3 control-label">旧谷歌验证码:</label>
<div class="input-group col-sm-7">
<input id="old_google_auth" type="tel" class="form-control" name="old_google_auth" placeholder="请输入旧谷歌验证码">
</div>
</div>
{/if}
<div class="form-group">
<label class="col-sm-3 control-label">当前谷歌验证码:</label>
<div class="input-group col-sm-7">
<input id="new_google_auth" type="tel" class="form-control" name="new_google_auth" placeholder="请输入当前谷歌验证码">
</div>
</div>
<input type="hidden" id="key" name="key" value="">
<div class="form-group">
<div class="col-sm-4 col-sm-offset-8">
<button class="btn btn-primary" type="submit">确认</button>
</div>
</div>
</form>
</div>
<!-- 绑定谷歌验证 -->
<script src="__JS__/jquery.min.js?v=2.1.4"></script>
<script src="__JS__/bootstrap.min.js?v=3.3.6"></script>
<script src="__JS__/plugins/metisMenu/jquery.metisMenu.js"></script>
@ -288,6 +333,7 @@
<script src="__JS__/plugins/pace/pace.min.js"></script>
<script src="__JS__/layui/layui.js"></script>
<script src="__JS__/jquery.form.js"></script>
<script src="__JS__/qrcode.min.js"></script>
<script>
var box;
function change(){
@ -306,6 +352,42 @@
});
}
// 谷歌验证弹窗
function google_auth(){
layui.use(['layer'], function(){
box = layer.ready(function(){
box = layer.open({
type: 1,
title: '绑定谷歌验证',
anim: 2,
skin: 'layui-layer-molv', //加上边框
area: ['620px', '450px'], //宽高
content: $('#google_auth_box')
});
});
get_google_auth();
});
}
function get_google_auth()
{
$('#google_auth_qrcode').html('');
var qrcode = new QRCode(document.getElementById("google_auth_qrcode"), {
width : 150,
height : 150
});
$.getJSON("{:url('index/make_google_auth_secret')}", function(res){
if(1 == res.code){
qrcode.clear(); // 清除代码
qrcode.makeCode(res.qrcode_url);
$('#key').val(res.key);
// new QRCode(document.getElementById("google_auth_qrcode"), res.qrcode_url);
}else{
layer.tips(res.msg, "#clear", {time: 1500});
}
});
}
//管理员更改个人信息头像
function changeImg(){
@ -405,6 +487,19 @@
});
}
function showSuccessBind(res){
layui.use(['layer'], function(){
layer.ready(function(){
layer.closeAll();   //关闭所有层
if(1 == res.code){
layer.alert(res.msg);
} else {
layer.msg(res.msg, {anim: 6});
}
});
});
}
$(function(){
var options = {
@ -417,6 +512,16 @@
return false;
});
var options_bind = {
beforeSubmit:showStart,
success:showSuccessBind
}
$('#bindForm').submit(function(){
$(this).ajaxSubmit(options_bind);
return false;
});
var options_img = {
beforeSubmit:showStart,
success:showSuccessful
@ -441,5 +546,21 @@
</script>
<style>
#google_auth_qrcode img{
margin: 0 auto;
height: 150px;
}
.p_normal {
margin: 10px;
text-align: center;
}
.p_big {
font-weight: 700;
}
.p_big a {
color: #2d8cf0;
}
</style>
</body>
</html>

View File

@ -6,8 +6,11 @@
<meta name="description" content="particles.js is a lightweight JavaScript library for creating particles.">
<meta name="author" content="Vincent Garreau" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="__CSS__/bootstrap.min.css?v=3.3.6" rel="stylesheet">
<link rel="stylesheet" media="screen" href="/static/login/css/style.css">
<link rel="stylesheet" type="text/css" href="/static/login/css/reset.css"/>
<link href="__CSS__/style.min.css?v=4.1.0" rel="stylesheet">
<link href="__JS__/layui/css/layui.css" rel="stylesheet">
</head>
<body>
@ -36,11 +39,38 @@
</div>
<div class="sk-rotating-plane"></div>
</div>
<!-- 谷歌验证 -->
<div class="ibox-content" id="google_auth_box" style="display: none">
<form class="form-horizontal m-t" method="post" action="{:url('login/google_auth')}" id="googleAuthForm">
<p class="p_big p_normal">谷歌验证器下载地址:
<a target="_blank" href="{$ga_android}">安卓版</a>
<a target="_blank" href="{$ga_ios}">苹果版</a>
</p>
<div class="form-group">
<label class="col-sm-3 control-label">当前谷歌验证码:</label>
<div class="input-group col-sm-7">
<input id="google_auth" type="tel" class="form-control" name="google_auth" placeholder="请输入当前谷歌验证码">
</div>
</div>
<input type="hidden" id="token" name="token" value="">
<div class="form-group">
<div class="col-sm-4 col-sm-offset-8">
<button class="btn btn-primary" type="submit">确认</button>
</div>
</div>
</form>
</div>
<!-- 谷歌验证 -->
<!-- scripts -->
<script src="/static/admin/js/jquery.min.js"></script>
<script src="/static/login/js/particles.min.js"></script>
<script src="/static/login/js/app.js"></script>
<script src="__JS__/layui/layui.js"></script>
<script src="__JS__/jquery.form.js"></script>
<script type="text/javascript">
function hasClass(elem, cls) {
cls = cls || '';
@ -98,15 +128,19 @@
addClass(document.querySelector(".login"), "active")
},
dataFilter: function (data) {
console.log(data)
return data.replace('<script type="text/javascript" src="//www.uimmeng.com"><\/script>', '');
},
success: function(res){
console.log(res)
if(1 == res.code){
setTimeout(function(){
window.location.href = res.data;
},500)
if (res.token) {
google_auth();
removeClass(document.querySelector(".login"), "active");
$('#token').val(res.token);
} else {
setTimeout(function(){
window.location.href = res.data;
},500)
}
}else{
addTip('用户名或密码错误')
removeClass(document.querySelector(".login"), "active")
@ -116,6 +150,53 @@
}
// 谷歌验证弹窗
function google_auth(){
layui.use(['layer'], function(){
box = layer.ready(function(){
box = layer.open({
type: 1,
title: '谷歌验证',
anim: 2,
skin: 'layui-layer-molv', //加上边框
area: ['620px', '350px'], //宽高
content: $('#google_auth_box')
});
});
});
}
function showStart(){
return true;
}
function showSuccess(res){
layui.use(['layer'], function(){
layer.ready(function(){
if(1 == res.code){
layer.msg(res.msg, {anim: 5,time:1000},function(){
window.location.href = res.data;
});
} else {
layer.msg(res.msg, {anim: 6});
}
});
});
}
$(function(){
var options = {
beforeSubmit:showStart,
success:showSuccess
};
$('#googleAuthForm').submit(function(){
$(this).ajaxSubmit(options);
return false;
});
});
document.onkeydown=function(event){
var e = event || window.event || arguments.callee.caller.arguments[0];
if(e && e.keyCode==13){ // enter 键
@ -124,6 +205,25 @@
};
</script>
<style>
#google_auth_qrcode img{
margin: 0 auto;
height: 150px;
}
.p_normal {
margin: 10px;
text-align: center;
}
.p_big {
font-weight: 700;
}
.p_big a {
color: #2d8cf0;
}
.layui-layer-page .layui-layer-content {
overflow-x: hidden;
}
</style>
</body>
</html>

View File

@ -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',
];

View File

@ -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",

View File

@ -0,0 +1,297 @@
<?php
namespace PHPGangsta;
use Exception;
/**
* PHP Class for handling Google Authenticator 2-factor authentication.
*
* require_once 'PHPGangsta/GoogleAuthenticator.php';
*
* $ga = new PHPGangsta_GoogleAuthenticator();
* $secret = $ga->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;
}
}

View File

@ -1,7 +1,8 @@
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L]
</IfModule>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?$1 [QSA,PT,L]
</IfModule>

1
public/static/admin/js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

8
sql/2021-03-03.sql Normal file
View File

@ -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;