mirror of
https://github.com/tznb1/TwoNav.git
synced 2025-08-10 08:51:49 +00:00
v2.0.34-20230809
This commit is contained in:
@@ -26,14 +26,18 @@ TwoNav 是一款开源免费的书签(导航)管理程序,界面简洁,
|
||||
* 支持加密链接
|
||||
* 支持分享链接
|
||||
* 支持二级分类
|
||||
* 支持用户分组
|
||||
* 支持用户分组/权限管理
|
||||
* 支持Chrome/Firefox/Edge书签批量导入
|
||||
* 支持多种主题风格
|
||||
* 支持批量更新链接图标/标题/描述等信息
|
||||
* 支持链接信息自动识别
|
||||
* 支持API
|
||||
* 支持Docker部署
|
||||
* 支持uTools插件
|
||||
* 支持Chromium内核的[浏览器扩展]
|
||||
* 支持简易文章管理
|
||||
* 支持更换各种模板/支持混搭,20+个主题模板
|
||||
* 安全性支持:更换登录入口/二级密码/OTP双重验证
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
161
system/Authenticator.php
Normal file
161
system/Authenticator.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
class PHPGangsta_GoogleAuthenticator
|
||||
{
|
||||
protected $_codeLength = 6;
|
||||
public function createSecret($secretLength = 16)
|
||||
{
|
||||
$validChars = $this->_getBase32LookupTable();
|
||||
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;
|
||||
}
|
||||
public function getCode($secret, $timeSlice = null)
|
||||
{
|
||||
if ($timeSlice === null) {
|
||||
$timeSlice = floor(time() / 30);
|
||||
}
|
||||
|
||||
$secretkey = $this->_base32Decode($secret);
|
||||
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
|
||||
$hm = hash_hmac('SHA1', $time, $secretkey, true);
|
||||
$offset = ord(substr($hm, -1)) & 0x0F;
|
||||
$hashpart = substr($hm, $offset, 4);
|
||||
$value = unpack('N', $hashpart);
|
||||
$value = $value[1];
|
||||
$value = $value & 0x7FFFFFFF;
|
||||
$modulo = pow(10, $this->_codeLength);
|
||||
return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
|
||||
}
|
||||
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://api.qrserver.com/v1/create-qr-code/?data=$urlencoded&size=${width}x${height}&ecc=$level";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function setCodeLength($length)
|
||||
{
|
||||
$this->_codeLength = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected function _getBase32LookupTable()
|
||||
{
|
||||
return array(
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
||||
'=',
|
||||
);
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
return $result === 0;
|
||||
}
|
||||
}
|
||||
@@ -392,7 +392,7 @@ if($_GET['type'] == 'upload'){
|
||||
}
|
||||
//数据库清除
|
||||
if(!empty($_POST['TABLE'])){
|
||||
$TABLE = ["user_categorys","user_links","user_pwd_group","user_share","user_apply"];
|
||||
$TABLE = ["user_categorys","user_links","user_pwd_group","user_share","user_apply","user_article_list"];
|
||||
foreach($_POST['TABLE'] as $key =>$value){
|
||||
if(in_array($key,$TABLE)){
|
||||
delete_db($key,['uid'=>UID]);
|
||||
@@ -418,7 +418,7 @@ if($_GET['type'] == 'upload'){
|
||||
|
||||
//文件删除
|
||||
if(!empty($_POST['FILE'])){
|
||||
$FILE = ["MessageBoard","favicon"];
|
||||
$FILE = ["MessageBoard","favicon","upload"];
|
||||
foreach($_POST['FILE'] as $key =>$value){
|
||||
$path = DIR.'/data/user/'.U.'/'.$key;
|
||||
if(in_array($key,$FILE) && is_dir($path)){
|
||||
|
||||
@@ -47,7 +47,7 @@ if(!defined('DIR')){
|
||||
$info['file_db'] = $info['backup_dir'] .'/'. $info['file'].'.db3';
|
||||
$info['file_info'] = $info['backup_dir'] .'/'. $info['file'].'.info';
|
||||
$info['file_gz'] = $info['backup_dir'] .'/'. $info['file'].'.tar';
|
||||
$info['table_arr'] = ['user_config','user_categorys','user_links','user_pwd_group','user_apply','user_share'];
|
||||
$info['table_arr'] = ['user_config','user_categorys','user_links','user_pwd_group','user_apply','user_share','user_article_list'];
|
||||
$info['lock'] = DIR.'/data/user/'.U.'/lock.'.UID;
|
||||
if (!extension_loaded('phar')) {
|
||||
msg(-1,'不支持phar扩展');
|
||||
@@ -167,7 +167,7 @@ if(!defined('DIR')){
|
||||
}
|
||||
|
||||
//遍历删除用户数据
|
||||
$info['table_arr'] = ['user_config','user_categorys','user_links','user_pwd_group','user_apply','user_share'];
|
||||
$info['table_arr'] = ['user_config','user_categorys','user_links','user_pwd_group','user_apply','user_share','user_article_list'];
|
||||
foreach($info['table_arr'] as $table_name){
|
||||
|
||||
//删除数据
|
||||
@@ -186,8 +186,15 @@ if(!defined('DIR')){
|
||||
$where['name'] = $table_name;
|
||||
$where['LIMIT'] = [($page - 1) * $limit,$limit];
|
||||
$datas = $MyDB->select('backup','data',$where);
|
||||
foreach($datas as $data){
|
||||
foreach($datas as $key => $data){
|
||||
$data = unserialize($data);
|
||||
//处理null
|
||||
foreach ($data as $key => $value) {
|
||||
if ($value === null) {
|
||||
$data[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($data['id'])){
|
||||
unset($data['id']);
|
||||
}
|
||||
|
||||
@@ -951,7 +951,7 @@ function write_security_setting(){
|
||||
'login_page'=>['v'=>['admin','index','auto'],'msg'=>'登录成功参数错误'],
|
||||
'Password2'=>['empty'=>true]
|
||||
];
|
||||
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);
|
||||
foreach ($datas as $key => $data){
|
||||
if($data['int']){
|
||||
$LoginConfig[$key] = ($_POST[$key] >= $data['min'] && $_POST[$key] <= $data['max'])?intval($_POST[$key]):msg(-1,$data['msg']);
|
||||
@@ -1211,6 +1211,65 @@ function write_user_password(){
|
||||
msg(1,'修改成功');
|
||||
}
|
||||
|
||||
|
||||
//读双重验证
|
||||
function read_totp(){
|
||||
global $USER_DB;
|
||||
if($USER_DB['Password'] !== Get_MD5_Password($_POST['Password'],$USER_DB['RegTime'])){
|
||||
msg(-1102,'密码错误,请核对后再试!');
|
||||
}
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);
|
||||
if(empty($LoginConfig['totp_key'])){
|
||||
require DIR . '/system/Authenticator.php';
|
||||
$totp = new PHPGangsta_GoogleAuthenticator();
|
||||
$key = $totp->createSecret();
|
||||
msgA(['code'=>2,'msg'=>'未开启双重验证','key'=> $key ]);
|
||||
}
|
||||
msgA(['code'=>1,'msg'=>'已开启双重验证']);
|
||||
}
|
||||
|
||||
//写双重验证
|
||||
function write_totp(){
|
||||
global $USER_DB;
|
||||
if($USER_DB['Password'] !== Get_MD5_Password($_POST['Password'],$USER_DB['RegTime'])){
|
||||
msg(-1102,'密码错误,请核对后再试!');
|
||||
}
|
||||
|
||||
if($_GET['type'] === 'delete'){ //删除双重验证
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);
|
||||
if(empty($LoginConfig['totp_key'])){
|
||||
msgA(['code'=>-1,'msg'=>'未开启双重验证',]);
|
||||
}
|
||||
$LoginConfig['totp_key'] = '';
|
||||
update_db("global_user", ["LoginConfig"=>$LoginConfig],["ID"=>UID],[1,'操作成功']);
|
||||
}elseif($_GET['type'] === 'set'){ //设置双重验证
|
||||
//必填项验证
|
||||
if(empty($_POST['key'])){
|
||||
msgA(['code'=>-1,'msg'=>'Key不能为空']);
|
||||
}elseif(empty($_POST['code'])){
|
||||
msgA(['code'=>-1,'msg'=>'验证码不能为空']);
|
||||
}
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);
|
||||
if(!empty($LoginConfig['totp_key'])){
|
||||
msgA(['code'=>-1,'msg'=>'已开启双重验证,无法继续开启!']);
|
||||
}
|
||||
//载入totp库
|
||||
require DIR . '/system/Authenticator.php';
|
||||
$totp = new PHPGangsta_GoogleAuthenticator();
|
||||
$checkResult = $totp->verifyCode($_POST['key'], $_POST['code'], 2);
|
||||
if(!$checkResult){
|
||||
msgA(['code'=>-1,'msg'=>'验证失败,请重试']);
|
||||
}
|
||||
//写入数据库
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);
|
||||
$LoginConfig['totp_key'] = $_POST['key'];
|
||||
update_db("global_user", ["LoginConfig"=>$LoginConfig],["ID"=>UID],[1,'操作成功']);
|
||||
}else{
|
||||
msg(-1,'请求参数有误');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//查Token
|
||||
function read_token(){
|
||||
global $USER_DB;
|
||||
|
||||
@@ -40,11 +40,25 @@ if(strlen($Password)!==32){
|
||||
msg(-1,"浏览器UA长度异常,请更换浏览器!");
|
||||
}
|
||||
|
||||
$LoginConfig = unserialize( $USER_DB['LoginConfig'] );
|
||||
//开启双重验证时验证OTP验证码
|
||||
if(!empty($LoginConfig['totp_key'])){
|
||||
if(empty($_POST['otp_code'])){
|
||||
msgA(['code'=>-1,'msg'=>'您已开启双重验证,请输入OTP验证码']);
|
||||
}
|
||||
require DIR . '/system/Authenticator.php';
|
||||
$totp = new PHPGangsta_GoogleAuthenticator();
|
||||
$checkResult = $totp->verifyCode($LoginConfig['totp_key'], $_POST['otp_code'], 2);
|
||||
if(!$checkResult){
|
||||
msgA(['code'=>-1,'msg'=>'OTP验证码错误,请重试!']);
|
||||
}
|
||||
}
|
||||
|
||||
//计算请求密码和数据库的对比
|
||||
if(Get_MD5_Password($Password,$USER_DB["RegTime"]) === $USER_DB["Password"]){
|
||||
update_db("user_log", ["description" => "请求登录>登录成功"], ["id"=>$log_id]);
|
||||
Set_key($USER_DB);
|
||||
$LoginConfig = unserialize( $USER_DB['LoginConfig'] );
|
||||
|
||||
if(empty($LoginConfig['login_page']) || $LoginConfig['login_page'] == 'admin'){
|
||||
$url = "./?c=admin&u={$USER_DB['User']}";
|
||||
}elseif($LoginConfig['login_page'] == 'index'){
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.33-20230802
|
||||
v2.0.34-20230809
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php $title='安全设置'; require 'header.php'; ?>
|
||||
<?php $title='安全设置'; require 'header.php';
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);
|
||||
$LoginConfig['totp_key'] = empty($LoginConfig['totp_key']) ? '0':'1';?>
|
||||
<body>
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main">
|
||||
@@ -109,14 +111,58 @@
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block"><button class="layui-btn layui-btn-normal" lay-submit lay-filter="save">确认保存</button></div>
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="save">确认保存</button>
|
||||
<button class="layui-btn layui-bg-purple" lay-submit lay-filter="open_totp">OTP 双重验证</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="ul_totp" style="margin-top:18px;display:none;padding-right: 10px;">
|
||||
<form class="layui-form" lay-filter="ul_totp">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">二维码</label>
|
||||
<div id="qr"></div><div id="qrcode"></div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">秘钥</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="key" id="key" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">为了您的账户安全,成功保存后无法再查看秘钥,请勿泄漏秘钥</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">验证码</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="code" id="code" class="layui-input">
|
||||
</div>
|
||||
<div class="layui-form-mid layui-word-aux">请输入生成的验证码</div>
|
||||
</div>
|
||||
|
||||
<pre class="layui-code" >
|
||||
这东西叫法太多了,比如双重验证/动态密码/动态口令/动态令牌/身份验证器/双因子认证/2FA/TOTP验证码等等
|
||||
原理是基于时间的动态验证码,网上客户端也大把,喜欢那个安装那个
|
||||
开启后登录时需输入OTP验证码,作用是提高账号安全性
|
||||
</pre>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn layui-btn-warm" type="button" id="close" >关闭</button>
|
||||
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="save_totp" id="save_totp">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ul>
|
||||
|
||||
<script src = "<?php echo $libs;?>/jquery/jquery-3.6.0.min.js"></script>
|
||||
<script src = "<?php echo $libs;?>/jquery/jquery.md5.js"></script>
|
||||
<script src = "<?php echo $libs; ?>/jquery/jquery.qrcode.min.js"></script>
|
||||
<script src = "./templates/admin/js/public.js?v=<?php echo $Ver;?>"></script>
|
||||
<?php load_static('js');?>
|
||||
<script>
|
||||
@@ -126,9 +172,9 @@ layui.use(['jquery','form','miniTab'], function () {
|
||||
miniTab = layui.miniTab;
|
||||
miniTab.listen();
|
||||
//表单赋值
|
||||
form.val('form', <?php echo json_encode(unserialize( $USER_DB['LoginConfig'] ));?>);
|
||||
form.val('form', <?php echo json_encode($LoginConfig);?>);
|
||||
|
||||
//监听提交
|
||||
//保存
|
||||
form.on('submit(save)', function (data) {
|
||||
$("*").blur(); //失去焦点,解决按回车无限提交
|
||||
data.field.Password=$.md5(data.field.Password);
|
||||
@@ -136,7 +182,7 @@ layui.use(['jquery','form','miniTab'], function () {
|
||||
if(data.code == 1) {
|
||||
var index = layer.alert("保存成功!", function () {
|
||||
layer.close(index);
|
||||
//miniTab.deleteCurrentByIframe();
|
||||
//miniTab.deleteCurrentByIframe(); //关闭页面
|
||||
});
|
||||
}else{
|
||||
layer.msg(data.msg, {icon: 5});
|
||||
@@ -144,6 +190,69 @@ layui.use(['jquery','form','miniTab'], function () {
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
//双重验证
|
||||
form.on('submit(open_totp)', function (data) {
|
||||
$("*").blur(); //失去焦点,解决按回车无限提交
|
||||
data.field.Password=$.md5(data.field.Password);
|
||||
pwd_md5 = data.field.Password;
|
||||
$.post(get_api('read_totp'),data.field,function(data,status){
|
||||
if(data.code == 1){
|
||||
layer.confirm('已开启双重验证,是否要关闭?',{icon: 3, title:'温馨提示'}, function(index){
|
||||
layer.closeAll();
|
||||
$.post(get_api('write_totp','delete'),{'Password':pwd_md5},function(data,status){
|
||||
if(data.code == 1) {
|
||||
layer.msg(data.msg, {icon: 1});
|
||||
}else{
|
||||
layer.msg(data.msg, {icon: 5});
|
||||
}
|
||||
});
|
||||
});
|
||||
}else if(data.code == 2) {
|
||||
layer.confirm('未开启双重验证,是否要开启?',{icon: 3, title:'温馨提示'}, function(index){
|
||||
layer.closeAll();
|
||||
$('#key').val(data.key);
|
||||
$('#code').val('');
|
||||
$("#qr").html('');//防止多次操作出现多个二维码
|
||||
let content = `otpauth://totp/${u}?secret=${data.key}&issuer=TwoNav`;
|
||||
$('#qr').qrcode({render: "canvas",width: 200,height: 200,text: content});
|
||||
var index = layer.open({type: 1,scrollbar: false,shadeClose: true,title: '双重验证',area : ['100%', '100%'],content: $('.ul_totp')});
|
||||
});
|
||||
return false;
|
||||
}else{
|
||||
layer.msg(data.msg, {icon: 5});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
$('#key').on('input', function() {
|
||||
$("#key").html('');
|
||||
let key = $('#key').val();
|
||||
let content = `otpauth://totp/${u}?secret=${key}&issuer=TwoNav`;
|
||||
$("#qr").html('');
|
||||
$('#qr').qrcode({render: "canvas",width: 200,height: 200,text: content});
|
||||
});
|
||||
//保存双重验证
|
||||
form.on('submit(save_totp)', function (data) {
|
||||
$("*").blur(); //失去焦点,解决按回车无限提交
|
||||
data.field.Password = pwd_md5;
|
||||
$.post(get_api('write_totp','set'),data.field,function(data,status){
|
||||
if(data.code == 1) {
|
||||
layer.closeAll();
|
||||
layer.msg(data.msg, {icon: 1});
|
||||
}else{
|
||||
layer.msg(data.msg, {icon: 5});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
//关闭页面
|
||||
$(document).on('click', '#close', function() {
|
||||
layer.closeAll();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
<div class="layui-colla-item">
|
||||
<div class="layui-colla-title">API模式的差别</div>
|
||||
<div class="layui-colla-content">
|
||||
<p>安全模式: 仅提供TwoNav自身的API接口,访客(未登录/Token为空)无法调用!</p>
|
||||
<p>兼容模式: 兼容部分OneNav的API接口,以便于其他插件调用!不允许访客调用!</p>
|
||||
<p>兼容模式+开放: 在兼容模式的基础上允许访客调用API获取共有数据!</p>
|
||||
<p>安全模式: 仅提供TwoNav自身的API接口,不兼容Onenav的API接口!</p>
|
||||
<p>兼容模式: 兼容部分OneNav的API接口,以便于其他插件调用!不支持访客调用!</p>
|
||||
<p>如果你未使用相关扩展插件,则无需修改模式并将Token删除,以提高账号的安全性!</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,10 +23,12 @@
|
||||
<div class="layui-colla-title">如何使用Chrome浏览器扩展 [非官方]</div>
|
||||
<div class="layui-colla-content">
|
||||
前言: 由于浏览器扩展插件非TwoNav所开发适配,如存在Bug或无法使用属正常现象!<br />
|
||||
安装: 谷歌应用商店下载<a href="https://chrome.google.com/webstore/detail/onenav/omlkjgkogkfpjbdigianpdbjncdchdco?hl=zh-CN&authuser=0" >OneNav</a>并安装 ( 已知0.9.24可用,其他版本未知 )<br />
|
||||
设置S: 1.TwoNav后台>右上角账号>安全设置>API模式>设为<兼容模式>或<兼容模式+开放> 2.在本页面获取Token<br />
|
||||
安装: 谷歌应用商店下载<a href="https://chrome.google.com/webstore/detail/onenav/omlkjgkogkfpjbdigianpdbjncdchdco?hl=zh-CN&authuser=0" >OneNav</a>并安装 ( 已知0.9.24 - 1.0.1可用,其他版本未知 )<br />
|
||||
设置S: 1.TwoNav后台>右上角账号>安全设置>API模式>设为<兼容模式> 2.在本页面获取Token<br />
|
||||
设置C: 插件API设置>填入域名和Token并保存>完成<br />
|
||||
常见问题1: 对于单用户使用,确保系统设置中默认用户是当前用户即可!多用户使用时需开启二级域名功能并将域名替换成用户的二级域名,注意结尾不需要带/
|
||||
问题1: 对于单用户使用,确保系统设置中默认用户是当前用户即可!多用户使用时需开启二级域名功能并将域名替换成用户的二级域名,注意结尾不需要带/
|
||||
问题2: 因为插件非官方开发维护,能用就尽量不要更新,如果插件更新可能会导致无法正常使用,需这个更新获得兼容性!
|
||||
问题3: 因为国内环境限制,你可能无法访问谷歌,这种情况你可以在交流群获取插件(安装方法自行百度,部分浏览器可能需要开发者模式加载)
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-colla-item">
|
||||
@@ -35,7 +36,7 @@
|
||||
<div class="layui-colla-content">
|
||||
<p>前言: 由于uTools扩展插件非TwoNav所开发适配,如存在Bug或无法使用属正常现象!</p>
|
||||
<p>安装: 在uTools插件应用市场>搜索OneNav>点击获取 </p>
|
||||
<p>设置S: 1.TwoNav后台>右上角账号>安全设置>API模式>设为<兼容模式>或<兼容模式+开放> 2.在本页面获取SecretKey ( 即插件设置中的API KEY )</p>
|
||||
<p>设置S: 1.TwoNav后台>右上角账号>安全设置>API模式>设为<兼容模式> 2.在本页面获取SecretKey ( 即插件设置中的API KEY )</p>
|
||||
<p>设置C: 打开uTools中的OneNav,点击右下角小齿轮>输入网站地址/用户名/API KEY</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
</blockquote>
|
||||
|
||||
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;"><legend>本地备份 (订阅可用)</legend></fieldset>
|
||||
<blockquote class="layui-elem-quote" style="margin-top: 10px;border-left: 2px solid #FF5722; color: #FF5722;">1.备份数据库仅保存最近20份数据<br />2.该功能仅辅助备份使用,无法确保100%数据安全,因此定期对整个站点打包备份仍然是必要的</blockquote>
|
||||
<blockquote class="layui-elem-quote" style="margin-top: 10px;border-left: 2px solid #FF5722; color: #FF5722;">1.备份数据库仅保存最近20份数据<br />2.该功能仅辅助备份使用,无法确保100%数据安全,因此定期对整个站点打包备份仍然是必要的<br />3.不支持将新版本备份回滚到旧版本中,不建议跨数据库类型回滚</blockquote>
|
||||
<!-- 数据表格 -->
|
||||
<table class="layui-hide" id="list" lay-filter="list"></table>
|
||||
<!--本地备份备注输入-->
|
||||
@@ -233,8 +233,10 @@
|
||||
<input type="checkbox" name="TABLE[user_pwd_group]" title="加密" checked>
|
||||
<input type="checkbox" name="TABLE[user_share]" title="分享" checked>
|
||||
<input type="checkbox" name="TABLE[user_apply]" title="收录" checked>
|
||||
<input type="checkbox" name="TABLE[user_article_list]" title="文章" checked>
|
||||
<input type="checkbox" name="FILE[MessageBoard]" title="留言" checked>
|
||||
<input type="checkbox" name="FILE[favicon]" title="图标" checked>
|
||||
<input type="checkbox" name="FILE[upload]" title="上传目录(如文章图片)" checked>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
<body>
|
||||
<div class="layuimini-container">
|
||||
<div class="layuimini-main" style=" margin-left: 20px;">
|
||||
<li class="layui-timeline-item">
|
||||
<i class="layui-icon layui-timeline-axis"></i>
|
||||
<div class="layui-timeline-content layui-text">
|
||||
<h4 class="layui-timeline-title">v2.0.34-20230809</h4>
|
||||
<ul>
|
||||
<li>[新增] 安全设置新增OTP双重验证</li>
|
||||
<li>[模板] 所有登录模板:已开启双重验证时,支持输入OTP验证码,版本:2.0.4 </li>
|
||||
<li>[警告] 如果您正在使用非默认登录模板,请立即更新登录模板,以免因模板不支持输入OTP验证码造成无法登录</li>
|
||||
<li>[新增] 导出导入>清空数据>支持清空文章和上传目录(upload)</li>
|
||||
<li>[新增] 导出导入>本地备份>支持备份和回滚文章列表</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="layui-timeline-item">
|
||||
<i class="layui-icon layui-timeline-axis"></i>
|
||||
<div class="layui-timeline-content layui-text">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php if(!defined('DIR')){header('HTTP/1.1 404 Not Found');header("status: 404 Not Found");exit;}?>
|
||||
<?php if(!defined('DIR')){header('HTTP/1.1 404 Not Found');header("status: 404 Not Found");exit;}
|
||||
$LoginConfig = unserialize($USER_DB['LoginConfig']);?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -29,16 +30,21 @@
|
||||
<form class="layui-form login-bottom">
|
||||
<div class="center">
|
||||
<div class="item">
|
||||
<span class="icon icon-2"></span>
|
||||
<span class="icon layui-icon layui-icon-username"></span>
|
||||
<input type="text" name="User" lay-verify="required" placeholder="请输入账号">
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<span class="icon icon-3"></span>
|
||||
<span class="icon layui-icon layui-icon-password"></span>
|
||||
<input type="password" name="Password" lay-verify="required" placeholder="请输入密码">
|
||||
<span class="bind-password icon icon-4"></span>
|
||||
</div>
|
||||
|
||||
<?php if(!empty($LoginConfig['totp_key'])){ ?>
|
||||
<div class="item">
|
||||
<span class="icon layui-icon layui-icon-vercode"></span>
|
||||
<input type="text" name="otp_code" lay-verify="required" placeholder="请输入OTP验证码">
|
||||
</div>
|
||||
<?php }?>
|
||||
</div>
|
||||
<div class="tip">
|
||||
<?php
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "默认",
|
||||
"description": "默认",
|
||||
"homepage": "https://gitee.com/tznb/TwoNav",
|
||||
"version": "2.0.2",
|
||||
"update": "2023/04/25",
|
||||
"version": "2.0.4",
|
||||
"update": "2023/08/09",
|
||||
"author": "TwoNav"
|
||||
}
|
||||
Reference in New Issue
Block a user