feat: 实现非对称加密关键接口

This commit is contained in:
kuaifan 2023-03-27 10:09:00 +08:00
parent ef7ed58a22
commit 7e98a78333
43 changed files with 724 additions and 394 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
/public/.user.ini /public/.user.ini
/storage/*.key /storage/*.key
/config/LICENSE /config/LICENSE
/config/PGP_*
/vendor /vendor
/build /build
/tmp /tmp

View File

@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.25.48]
### Performance
- 自动清空文件回收站
## [0.25.42] ## [0.25.42]
### Performance ### Performance

View File

@ -9889,12 +9889,12 @@
* Clones a request and overrides some of its parameters. * Clones a request and overrides some of its parameters.
* *
* @return static * @return static
* @param array $query The GET parameters * @param array|null $query The GET parameters
* @param array $request The POST parameters * @param array|null $request The POST parameters
* @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) * @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array $cookies The COOKIE parameters * @param array|null $cookies The COOKIE parameters
* @param array $files The FILES parameters * @param array|null $files The FILES parameters
* @param array $server The SERVER parameters * @param array|null $server The SERVER parameters
* @return static * @return static
* @static * @static
*/ */

View File

@ -534,7 +534,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/one 10. 获取单条消息 * @api {get} api/dialog/msg/one 11. 获取单条消息
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -563,7 +563,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/read 11. 已读聊天消息 * @api {get} api/dialog/msg/read 12. 已读聊天消息
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -614,7 +614,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/unread 12. 获取未读消息数据 * @api {get} api/dialog/msg/unread 13. 获取未读消息数据
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -655,7 +655,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {post} api/dialog/msg/sendtext 13. 发送消息 * @api {post} api/dialog/msg/sendtext 14. 发送消息
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -676,7 +676,6 @@ class DialogController extends AbstractController
*/ */
public function msg__sendtext() public function msg__sendtext()
{ {
Base::checkClientVersion('0.19.0');
$user = User::auth(); $user = User::auth();
// //
if (!$user->bot) { if (!$user->bot) {
@ -691,11 +690,11 @@ class DialogController extends AbstractController
} }
} }
// //
$dialog_id = Base::getPostInt('dialog_id'); $dialog_id = intval(Request::input('dialog_id'));
$update_id = Base::getPostInt('update_id'); $update_id = intval(Request::input('update_id'));
$reply_id = Base::getPostInt('reply_id'); $reply_id = intval(Request::input('reply_id'));
$text = trim(Base::getPostValue('text')); $text = trim(Request::input('text'));
$silence = trim(Base::getPostValue('silence')) === 'yes'; $silence = trim(Request::input('silence')) === 'yes';
// //
WebSocketDialog::checkDialog($dialog_id); WebSocketDialog::checkDialog($dialog_id);
// //
@ -745,7 +744,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {post} api/dialog/msg/sendrecord 14. 发送语音 * @api {post} api/dialog/msg/sendrecord 15. 发送语音
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -765,15 +764,15 @@ class DialogController extends AbstractController
{ {
$user = User::auth(); $user = User::auth();
// //
$dialog_id = Base::getPostInt('dialog_id'); $dialog_id = intval(Request::input('dialog_id'));
$reply_id = Base::getPostInt('reply_id'); $reply_id = intval(Request::input('reply_id'));
// //
WebSocketDialog::checkDialog($dialog_id); WebSocketDialog::checkDialog($dialog_id);
// //
$action = $reply_id > 0 ? "reply-$reply_id" : ""; $action = $reply_id > 0 ? "reply-$reply_id" : "";
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; $path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$base64 = Base::getPostValue('base64'); $base64 = Request::input('base64');
$duration = Base::getPostInt('duration'); $duration = intval(Request::input('duration'));
if ($duration < 600) { if ($duration < 600) {
return Base::retError('说话时间太短'); return Base::retError('说话时间太短');
} }
@ -792,7 +791,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {post} api/dialog/msg/sendfile 15. 文件上传 * @api {post} api/dialog/msg/sendfile 16. 文件上传
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -814,16 +813,16 @@ class DialogController extends AbstractController
{ {
$user = User::auth(); $user = User::auth();
// //
$dialog_id = Base::getPostInt('dialog_id'); $dialog_id = intval(Request::input('dialog_id'));
$reply_id = Base::getPostInt('reply_id'); $reply_id = intval(Request::input('reply_id'));
$image_attachment = Base::getPostInt('image_attachment'); $image_attachment = intval(Request::input('image_attachment'));
// //
$dialog = WebSocketDialog::checkDialog($dialog_id); $dialog = WebSocketDialog::checkDialog($dialog_id);
// //
$action = $reply_id > 0 ? "reply-$reply_id" : ""; $action = $reply_id > 0 ? "reply-$reply_id" : "";
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; $path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$image64 = Base::getPostValue('image64'); $image64 = Request::input('image64');
$fileName = Base::getPostValue('filename'); $fileName = Request::input('filename');
if ($image64) { if ($image64) {
$data = Base::image64save([ $data = Base::image64save([
"image64" => $image64, "image64" => $image64,
@ -876,7 +875,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/sendfileid 16. 通过文件ID发送文件 * @api {get} api/dialog/msg/sendfileid 17. 通过文件ID发送文件
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -946,7 +945,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {post} api/dialog/msg/sendanon 16. 发送匿名消息 * @api {post} api/dialog/msg/sendanon 18. 发送匿名消息
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -964,8 +963,8 @@ class DialogController extends AbstractController
{ {
User::auth(); User::auth();
// //
$userid = Base::getPostInt('userid'); $userid = intval(Request::input('userid'));
$text = trim(Base::getPostValue('text')); $text = trim(Request::input('text'));
// //
$anonMessage = Base::settingFind('system', 'anon_message', 'open'); $anonMessage = Base::settingFind('system', 'anon_message', 'open');
if ($anonMessage != 'open') { if ($anonMessage != 'open') {
@ -999,7 +998,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/readlist 17. 获取消息阅读情况 * @api {get} api/dialog/msg/readlist 19. 获取消息阅读情况
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1028,7 +1027,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/detail 18. 消息详情 * @api {get} api/dialog/msg/detail 20. 消息详情
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1076,7 +1075,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/download 19. 文件下载 * @api {get} api/dialog/msg/download 21. 文件下载
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1119,7 +1118,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/withdraw 20. 聊天消息撤回 * @api {get} api/dialog/msg/withdraw 22. 聊天消息撤回
* *
* @apiDescription 消息撤回限制24小时内需要token身份 * @apiDescription 消息撤回限制24小时内需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1145,7 +1144,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/mark 21. 消息标记操作 * @api {get} api/dialog/msg/mark 23. 消息标记操作
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1212,7 +1211,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/silence 22. 消息免打扰 * @api {get} api/dialog/msg/silence 24. 消息免打扰
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1275,7 +1274,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/forward 23. 转发消息给 * @api {get} api/dialog/msg/forward 25. 转发消息给
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1312,7 +1311,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/emoji 24. emoji回复 * @api {get} api/dialog/msg/emoji 26. emoji回复
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1347,7 +1346,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/tag 25. 标注/取消标注 * @api {get} api/dialog/msg/tag 27. 标注/取消标注
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1376,7 +1375,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/todo 26. 设待办/取消待办 * @api {get} api/dialog/msg/todo 28. 设待办/取消待办
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1419,7 +1418,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/todolist 27. 获取消息待办情况 * @api {get} api/dialog/msg/todolist 29. 获取消息待办情况
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1449,7 +1448,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/msg/done 28. 完成待办 * @api {get} api/dialog/msg/done 30. 完成待办
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1496,7 +1495,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/add 29. 新增群组 * @api {get} api/dialog/group/add 31. 新增群组
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1558,7 +1557,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/edit 30. 修改群组 * @api {get} api/dialog/group/edit 32. 修改群组
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -1619,7 +1618,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/adduser 31. 添加群成员 * @api {get} api/dialog/group/adduser 33. 添加群成员
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* - 有群主时:只有群主可以邀请 * - 有群主时:只有群主可以邀请
@ -1655,7 +1654,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/deluser 32. 移出(退出)群成员 * @api {get} api/dialog/group/deluser 34. 移出(退出)群成员
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* - 只有群主、邀请人可以踢人 * - 只有群主、邀请人可以踢人
@ -1699,7 +1698,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/transfer 33. 转让群组 * @api {get} api/dialog/group/transfer 35. 转让群组
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* - 只有群主且是个人类型群可以解散 * - 只有群主且是个人类型群可以解散
@ -1743,7 +1742,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/disband 34. 解散群组 * @api {get} api/dialog/group/disband 36. 解散群组
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* - 只有群主且是个人类型群可以解散 * - 只有群主且是个人类型群可以解散
@ -1771,7 +1770,7 @@ class DialogController extends AbstractController
} }
/** /**
* @api {get} api/dialog/group/searchuser 35. 搜索个人群(仅限管理员) * @api {get} api/dialog/group/searchuser 37. 搜索个人群(仅限管理员)
* *
* @apiDescription 需要token身份用于创建部门搜索个人群组 * @apiDescription 需要token身份用于创建部门搜索个人群组
* @apiVersion 1.0.0 * @apiVersion 1.0.0

View File

@ -595,8 +595,8 @@ class FileController extends AbstractController
{ {
$user = User::auth(); $user = User::auth();
// //
$id = Base::getPostInt('id'); $id = intval(Request::input('id'));
$content = Base::getPostValue('content'); $content = Request::input('content');
// //
$file = File::permissionFind($id, $user, 1); $file = File::permissionFind($id, $user, 1);
// //

View File

@ -1543,7 +1543,8 @@ class ProjectController extends AbstractController
public function task__add() public function task__add()
{ {
User::auth(); User::auth();
parse_str(Request::getContent(), $data); //
$data = Request::input();
$project_id = intval($data['project_id']); $project_id = intval($data['project_id']);
$column_id = $data['column_id']; $column_id = $data['column_id'];
// 项目 // 项目
@ -1663,7 +1664,7 @@ class ProjectController extends AbstractController
{ {
User::auth(); User::auth();
// //
parse_str(Request::getContent(), $data); $data = Request::input();
$task_id = intval($data['task_id']); $task_id = intval($data['task_id']);
// //
$task = ProjectTask::userTask($task_id, true, true, 2); $task = ProjectTask::userTask($task_id, true, true, 2);
@ -1989,8 +1990,8 @@ class ProjectController extends AbstractController
{ {
User::auth(); User::auth();
// //
$project_id = intval(Base::getContentValue('project_id')); $project_id = intval(Request::input('project_id'));
$flows = Base::getContentValue('flows'); $flows = Request::input('flows');
// //
if (!is_array($flows)) { if (!is_array($flows)) {
return Base::retError('参数错误'); return Base::retError('参数错误');

View File

@ -137,14 +137,14 @@ class ReportController extends AbstractController
$user = User::auth(); $user = User::auth();
// //
$input = [ $input = [
"id" => Base::getPostValue("id", 0), "id" => Request::input("id", 0),
"sign" => Base::getPostValue("sign"), "sign" => Request::input("sign"),
"title" => Base::getPostValue("title"), "title" => Request::input("title"),
"type" => Base::getPostValue("type"), "type" => Request::input("type"),
"content" => Base::getPostValue("content"), "content" => Request::input("content"),
"receive" => Base::getPostValue("receive"), "receive" => Request::input("receive"),
// 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1上一天同理。 // 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1上一天同理。
"offset" => Base::getPostValue("offset", 0), "offset" => Request::input("offset", 0),
]; ];
$validator = Validator::make($input, [ $validator = Validator::make($input, [
'id' => 'numeric', 'id' => 'numeric',

View File

@ -10,7 +10,6 @@ use App\Module\BillExport;
use App\Module\BillMultipleExport; use App\Module\BillMultipleExport;
use App\Module\Doo; use App\Module\Doo;
use App\Module\Extranet; use App\Module\Extranet;
use Arr;
use Carbon\Carbon; use Carbon\Carbon;
use Guanguans\Notify\Factory; use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage; use Guanguans\Notify\Messages\EmailMessage;
@ -439,7 +438,7 @@ class SystemController extends AbstractController
$type = trim(Request::input('type')); $type = trim(Request::input('type'));
if ($type == 'save') { if ($type == 'save') {
User::auth('admin'); User::auth('admin');
$list = Base::getPostValue('list'); $list = Request::input('list');
$array = []; $array = [];
if (empty($list) || !is_array($list)) { if (empty($list) || !is_array($list)) {
return Base::retError('参数错误'); return Base::retError('参数错误');
@ -488,7 +487,7 @@ class SystemController extends AbstractController
$type = trim(Request::input('type')); $type = trim(Request::input('type'));
if ($type == 'save') { if ($type == 'save') {
User::auth('admin'); User::auth('admin');
$list = Base::getPostValue('list'); $list = Request::input('list');
$array = []; $array = [];
if (empty($list) || !is_array($list)) { if (empty($list) || !is_array($list)) {
return Base::retError('参数错误'); return Base::retError('参数错误');
@ -514,7 +513,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {post} api/system/license 08. License * @api {post} api/system/license 10. License
* *
* @apiDescription 获取License信息、保存License限管理员 * @apiDescription 获取License信息、保存License限管理员
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -536,7 +535,7 @@ class SystemController extends AbstractController
// //
$type = trim(Request::input('type')); $type = trim(Request::input('type'));
if ($type == 'save') { if ($type == 'save') {
$license = Base::getPostValue('license'); $license = Request::input('license');
Doo::licenseSave($license); Doo::licenseSave($license);
} }
// //
@ -550,7 +549,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/info 10. 获取终端详细信息 * @api {get} api/system/get/info 11. 获取终端详细信息
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -579,7 +578,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/ip 11. 获取IP地址 * @api {get} api/system/get/ip 12. 获取IP地址
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -594,7 +593,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/cnip 12. 是否中国IP地址 * @api {get} api/system/get/cnip 13. 是否中国IP地址
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -611,7 +610,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/ipgcj02 13. 获取IP地址经纬度 * @api {get} api/system/get/ipgcj02 14. 获取IP地址经纬度
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -628,7 +627,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/ipinfo 14. 获取IP地址详细信息 * @api {get} api/system/get/ipinfo 15. 获取IP地址详细信息
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -645,7 +644,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {post} api/system/imgupload 15. 上传图片 * @api {post} api/system/imgupload 16. 上传图片
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -679,8 +678,8 @@ class SystemController extends AbstractController
$scale = [$width, $height, $whcut]; $scale = [$width, $height, $whcut];
} }
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/"; $path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64')); $image64 = trim(Request::input('image64'));
$fileName = trim(Base::getPostValue('filename')); $fileName = trim(Request::input('filename'));
if ($image64) { if ($image64) {
$data = Base::image64save([ $data = Base::image64save([
"image64" => $image64, "image64" => $image64,
@ -705,7 +704,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/imgview 16. 浏览图片空间 * @api {get} api/system/get/imgview 17. 浏览图片空间
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -801,7 +800,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {post} api/system/fileupload 17. 上传文件 * @api {post} api/system/fileupload 18. 上传文件
* *
* @apiDescription 需要token身份 * @apiDescription 需要token身份
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -822,8 +821,8 @@ class SystemController extends AbstractController
return Base::retError('身份失效,等重新登录'); return Base::retError('身份失效,等重新登录');
} }
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/"; $path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64')); $image64 = trim(Request::input('image64'));
$fileName = trim(Base::getPostValue('filename')); $fileName = trim(Request::input('filename'));
if ($image64) { if ($image64) {
$data = Base::image64save([ $data = Base::image64save([
"image64" => $image64, "image64" => $image64,
@ -843,7 +842,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/showitem 18. 首页显示ITEM * @api {get} api/system/get/showitem 19. 首页显示ITEM
* *
* @apiDescription 用于判断首页是否显示pro、github、更新日志... * @apiDescription 用于判断首页是否显示pro、github、更新日志...
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -875,7 +874,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/get/starthome 19. 启动首页设置信息 * @api {get} api/system/get/starthome 20. 启动首页设置信息
* *
* @apiDescription 用于判断注册是否需要启动首页 * @apiDescription 用于判断注册是否需要启动首页
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -895,7 +894,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/email/check 20. 邮件发送测试(限管理员) * @api {get} api/system/email/check 21. 邮件发送测试(限管理员)
* *
* @apiDescription 测试配置邮箱是否能发送邮件 * @apiDescription 测试配置邮箱是否能发送邮件
* @apiVersion 1.0.0 * @apiVersion 1.0.0
@ -941,7 +940,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/checkin/export 21. 导出签到数据(限管理员) * @api {get} api/system/checkin/export 22. 导出签到数据(限管理员)
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -1108,7 +1107,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/checkin/down 22. 下载导出的签到数据 * @api {get} api/system/checkin/down 23. 下载导出的签到数据
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system
@ -1134,7 +1133,7 @@ class SystemController extends AbstractController
} }
/** /**
* @api {get} api/system/version 23. 获取版本号 * @api {get} api/system/version 24. 获取版本号
* *
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup system * @apiGroup system

View File

@ -1527,7 +1527,7 @@ class UsersController extends AbstractController
return Base::retError('未开放修改权限,请联系管理员'); return Base::retError('未开放修改权限,请联系管理员');
} }
// //
$list = Base::getPostValue('list'); $list = Request::input('list');
$array = []; $array = [];
if (empty($list) || !is_array($list)) { if (empty($list) || !is_array($list)) {
return Base::retError('参数错误'); return Base::retError('参数错误');
@ -1618,4 +1618,46 @@ class UsersController extends AbstractController
} }
return Base::retSuccess('success', $row); return Base::retSuccess('success', $row);
} }
/**
* @api {get} api/users/key/client 28. 客户端KEY
*
* @apiDescription 获取客户端KEY用于加密数据发送给服务端
* @apiVersion 1.0.0
* @apiGroup users
* @apiName key__client
*
* @apiParam {String} [client_id] 客户端ID希望不变的除非清除浏览器缓存或者卸载应用
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function key__client()
{
$clientId = (trim(Request::input('client_id')) ?: Base::generatePassword(6)) . Doo::userId();
//
$cacheKey = "KeyPair::" . $clientId;
if (Cache::has($cacheKey)) {
$cacheData = Base::json2array(Cache::get($cacheKey));
if ($cacheData['private_key']) {
return Base::retSuccess('success', [
'type' => 'pgp',
'id' => $clientId,
'key' => $cacheData['public_key'],
]);
}
}
//
$name = Doo::userEmail() ?: Base::generatePassword(6);
$email = Doo::userEmail() ?: 'aa@bb.cc';
$data = Doo::pgpGenerateKeyPair($name, $email, Base::generatePassword());
Cache::put("KeyPair::" . $clientId, Base::array2json($data), Carbon::now()->addQuarter());
//
return Base::retSuccess('success', [
'type' => 'pgp',
'id' => $clientId,
'key' => $data['public_key'],
]);
}
} }

View File

@ -12,59 +12,8 @@ class VerifyCsrfToken extends Middleware
* @var array * @var array
*/ */
protected $except = [ protected $except = [
// 上传图片 // 接口部分
'api/system/imgupload/', 'api/*',
// 上传文件
'api/system/fileupload/',
// 保存任务优先级
'api/system/priority/',
// 保存创建项目列表模板
'api/system/column/template/',
// License 设置
'api/system/license/',
// 添加任务
'api/project/task/add/',
// 保存工作流
'api/project/flow/save/',
// 修改任务
'api/project/task/update/',
// 聊天发文本
'api/dialog/msg/sendtext/',
// 聊天发语音
'api/dialog/msg/sendrecord/',
// 聊天发文件
'api/dialog/msg/sendfile/',
// 聊天发匿名消息
'api/dialog/msg/sendanon/',
// 保存文件内容
'api/file/content/save/',
// 保存文件内容office
'api/file/content/office/',
// 保存文件内容(上传)
'api/file/content/upload/',
// 保存汇报
'api/report/store/',
// 签到设置
'api/users/checkin/save/',
// 签到上报
'api/public/checkin/report/',
// 发布桌面端 // 发布桌面端
'desktop/publish/', 'desktop/publish/',

View File

@ -4,9 +4,9 @@ namespace App\Http\Middleware;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); @error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Module\Base;
use App\Module\Doo; use App\Module\Doo;
use Closure; use Closure;
use Request;
class WebApi class WebApi
{ {
@ -22,18 +22,41 @@ class WebApi
global $_A; global $_A;
$_A = []; $_A = [];
if (Request::input('__Access-Control-Allow-Origin') || Request::header('__Access-Control-Allow-Origin')) { Doo::load();
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:GET,POST,PUT,DELETE,OPTIONS'); $encrypt = Doo::pgpParseStr($request->header('encrypt'));
header('Access-Control-Allow-Headers:Content-Type, platform, platform-channel, token, release, Access-Control-Allow-Origin'); if ($request->isMethod('post')) {
$version = $request->header('version');
if ($version && version_compare($version, '0.25.48', '<')) {
// 旧版本兼容 php://input
parse_str($request->getContent(), $content);
if ($content) {
$request->merge($content);
}
} elseif ($encrypt['encrypt_type'] === 'pgp' && $content = $request->input('encrypted')) {
// 新版本解密提交的内容
$content = Doo::pgpDecryptApi($content, $encrypt['encrypt_id']);
if ($content) {
$request->merge($content);
}
}
} }
// 强制 https
$APP_SCHEME = env('APP_SCHEME', 'auto'); $APP_SCHEME = env('APP_SCHEME', 'auto');
if (in_array(strtolower($APP_SCHEME), ['https', 'on', 'ssl', '1', 'true', 'yes'], true)) { if (in_array(strtolower($APP_SCHEME), ['https', 'on', 'ssl', '1', 'true', 'yes'], true)) {
$request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO); $request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO);
} }
Doo::load();
return $next($request); $response = $next($request);
// 加密返回内容
if ($encrypt['client_type'] === 'pgp' && $content = $response->getContent()) {
$response->setContent(json_encode([
'encrypted' => Doo::pgpEncryptApi($content, $encrypt['client_key'])
]));
}
return $response;
} }
} }

View File

@ -29,7 +29,7 @@ use Request;
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|File newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|File newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|File newQuery() * @method static \Illuminate\Database\Eloquent\Builder|File newQuery()
* @method static \Illuminate\Database\Query\Builder|File onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|File onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|File query() * @method static \Illuminate\Database\Eloquent\Builder|File query()
* @method static \Illuminate\Database\Eloquent\Builder|File whereCid($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereCid($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value)
@ -46,8 +46,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|File whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|File withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|File withTrashed()
* @method static \Illuminate\Database\Query\Builder|File withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|File withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class File extends AbstractModel class File extends AbstractModel

View File

@ -20,7 +20,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|FileContent newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|FileContent newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent newQuery() * @method static \Illuminate\Database\Eloquent\Builder|FileContent newQuery()
* @method static \Illuminate\Database\Query\Builder|FileContent onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|FileContent onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent query() * @method static \Illuminate\Database\Eloquent\Builder|FileContent query()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereContent($value) * @method static \Illuminate\Database\Eloquent\Builder|FileContent whereContent($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|FileContent whereCreatedAt($value)
@ -31,8 +31,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereText($value) * @method static \Illuminate\Database\Eloquent\Builder|FileContent whereText($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|FileContent withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|FileContent withTrashed()
* @method static \Illuminate\Database\Query\Builder|FileContent withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|FileContent withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class FileContent extends AbstractModel class FileContent extends AbstractModel

View File

@ -28,17 +28,17 @@ use Request;
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int $owner_userid * @property-read int $owner_userid
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectColumn> $projectColumn
* @property-read int|null $project_column_count * @property-read int|null $project_column_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectLog> $projectLog
* @property-read int|null $project_log_count * @property-read int|null $project_log_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectUser> $projectUser
* @property-read int|null $project_user_count * @property-read int|null $project_user_count
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null) * @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null) * @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Project newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Project newQuery()
* @method static \Illuminate\Database\Query\Builder|Project onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|Project onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|Project query() * @method static \Illuminate\Database\Eloquent\Builder|Project query()
* @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedUserid($value)
@ -52,8 +52,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserSimple($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereUserSimple($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|Project whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|Project withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|Project withTrashed()
* @method static \Illuminate\Database\Query\Builder|Project withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|Project withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class Project extends AbstractModel class Project extends AbstractModel

View File

@ -20,11 +20,11 @@ use Request;
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \App\Models\Project|null $project * @property-read \App\Models\Project|null $project
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTask[] $projectTask * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTask> $projectTask
* @property-read int|null $project_task_count * @property-read int|null $project_task_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectColumn onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn query() * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereColor($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereColor($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereCreatedAt($value)
@ -34,8 +34,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereProjectId($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereSort($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereSort($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|ProjectColumn withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectColumn withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class ProjectColumn extends AbstractModel class ProjectColumn extends AbstractModel

View File

@ -12,7 +12,7 @@ use App\Module\Base;
* @property string|null $name 流程名称 * @property string|null $name 流程名称
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectFlowItem[] $projectFlowItem * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectFlowItem> $projectFlowItem
* @property-read int|null $project_flow_item_count * @property-read int|null $project_flow_item_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newQuery()

View File

@ -52,18 +52,18 @@ use Request;
* @property-read bool $today * @property-read bool $today
* @property-read \App\Models\Project|null $project * @property-read \App\Models\Project|null $project
* @property-read \App\Models\ProjectColumn|null $projectColumn * @property-read \App\Models\ProjectColumn|null $projectColumn
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskFile[] $taskFile * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTaskFile> $taskFile
* @property-read int|null $task_file_count * @property-read int|null $task_file_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTaskTag> $taskTag
* @property-read int|null $task_tag_count * @property-read int|null $task_tag_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTaskUser> $taskUser
* @property-read int|null $task_user_count * @property-read int|null $task_user_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end, $type = 'taskTime') * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end, $type = 'taskTime')
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask query() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedFollow($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedFollow($value)
@ -92,8 +92,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereStartAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTask withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTask withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class ProjectTask extends AbstractModel class ProjectTask extends AbstractModel
@ -530,8 +530,6 @@ class ProjectTask extends AbstractModel
public function updateTask($data, &$updateMarking = []) public function updateTask($data, &$updateMarking = [])
{ {
AbstractModel::transaction(function () use ($data, &$updateMarking) { AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 判断版本
Base::checkClientVersion('0.19.0');
// 主任务 // 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null; $mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流 // 工作流

View File

@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newQuery() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog query() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereDeletedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereDeletedAt($value)
@ -28,8 +28,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class ProjectTaskPushLog extends AbstractModel class ProjectTaskPushLog extends AbstractModel

View File

@ -23,10 +23,10 @@ use JetBrains\PhpStorm\Pure;
* @property int $userid * @property int $userid
* @property string $content * @property string $content
* @property string $sign 汇报唯一标识 * @property string $sign 汇报唯一标识
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ReportReceive[] $Receives * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ReportReceive> $Receives
* @property-read int|null $receives_count * @property-read int|null $receives_count
* @property-read mixed $receives * @property-read mixed $receives
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $receivesUser * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $receivesUser
* @property-read int|null $receives_user_count * @property-read int|null $receives_user_count
* @property-read \App\Models\User|null $sendUser * @property-read \App\Models\User|null $sendUser
* @method static Builder|Report newModelQuery() * @method static Builder|Report newModelQuery()

View File

@ -17,7 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newQuery() * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newQuery()
* @method static \Illuminate\Database\Query\Builder|TaskWorker onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker query() * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker query()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereArgs($value) * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereArgs($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereCreatedAt($value)
@ -27,8 +27,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereStartAt($value) * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|TaskWorker withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker withTrashed()
* @method static \Illuminate\Database\Query\Builder|TaskWorker withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|TaskWorker withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class TaskWorker extends AbstractModel class TaskWorker extends AbstractModel

View File

@ -24,11 +24,11 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\WebSocketDialogUser[] $dialogUser * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\WebSocketDialogUser> $dialogUser
* @property-read int|null $dialog_user_count * @property-read int|null $dialog_user_count
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newQuery() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog query() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereAvatar($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereAvatar($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereCreatedAt($value)
@ -40,8 +40,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereOwnerId($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereOwnerId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class WebSocketDialog extends AbstractModel class WebSocketDialog extends AbstractModel

View File

@ -39,7 +39,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog * @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg onlyTrashed() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
@ -61,8 +61,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withTrashed() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed() * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg withoutTrashed()
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class WebSocketDialogMsg extends AbstractModel class WebSocketDialogMsg extends AbstractModel

View File

@ -1933,60 +1933,6 @@ class Base
} }
} }
/**
* php://input 字符串解析到变量并获取指定值
* @param $key
* @return array
*/
public static function getContentsParse($key)
{
parse_str(Request::getContent(), $input);
if ($key) {
$input = $input[$key] ?? array();
}
return is_array($input) ? $input : array($input);
}
/**
* php://input 字符串解析到变量并获取指定值
* @param $key
* @param null $default
* @return mixed|null
*/
public static function getContentValue($key, $default = null)
{
global $_A;
if (!isset($_A["__static_input_content"])) {
parse_str(Request::getContent(), $input);
$_A["__static_input_content"] = $input;
}
return $_A["__static_input_content"][$key] ?? $default;
}
/**
* @param $key
* @param null $default
* @return array|mixed|string|null
*/
public static function getPostValue($key, $default = null)
{
$value = self::getContentValue($key, $default);
if (!isset($value)) {
$value = Request::post($key, $default);
}
return $value;
}
/**
* @param $key
* @param null $default
* @return int
*/
public static function getPostInt($key, $default = null)
{
return intval(self::getPostValue($key, $default));
}
/** /**
* 多维 array_values * 多维 array_values
* @param $array * @param $array

View File

@ -4,12 +4,14 @@ namespace App\Module;
use App\Exceptions\ApiException; use App\Exceptions\ApiException;
use App\Models\User; use App\Models\User;
use Cache;
use Carbon\Carbon; use Carbon\Carbon;
use FFI; use FFI;
class Doo class Doo
{ {
private static $doo; private static $doo;
private static $passphrase = "LYHevk5n";
/** /**
* char转为字符串 * char转为字符串
@ -45,10 +47,21 @@ class Doo
char* md5s(char* text, char* password); char* md5s(char* text, char* password);
char* macs(); char* macs();
char* dooSN(); char* dooSN();
char* pgpGenerateKeyPair(char* name, char* email, char* passphrase);
char* pgpEncrypt(char* plainText, char* publicKey);
char* pgpDecrypt(char* cipherText, char* privateKey, char* passphrase);
EOF, "/usr/lib/doo/doo.so"); EOF, "/usr/lib/doo/doo.so");
$token = $token ?: Base::headerOrInput('token'); $token = $token ?: Base::headerOrInput('token');
$language = $language ?: Base::headerOrInput('language'); $language = $language ?: Base::headerOrInput('language');
self::$doo->initialize("/var/www", $token, $language); self::$doo->initialize("/var/www", $token, $language);
//
$priPath = config_path("PGP_PRIVATE");
$pubPath = config_path("PGP_PUBLIC");
if (!file_exists($priPath) || !file_exists($pubPath)) {
$data = self::pgpGenerateKeyPair("doo", "admin@admin.com", self::$passphrase);
file_put_contents($priPath, $data['private_key']);
file_put_contents($pubPath, $data['public_key']);
}
} }
/** /**
@ -299,4 +312,103 @@ class Doo
{ {
return self::string(self::doo()->dooSN()); return self::string(self::doo()->dooSN());
} }
/**
* 生成PGP密钥对
* @param $name
* @param $email
* @param string $passphrase
* @return array
*/
public static function pgpGenerateKeyPair($name, $email, string $passphrase = ""): array
{
return Base::json2array(self::string(self::doo()->pgpGenerateKeyPair($name, $email, $passphrase)));
}
/**
* PGP加密
* @param $plaintext
* @param $publicKey
* @return string
*/
public static function pgpEncrypt($plaintext, $publicKey): string
{
if (strlen($publicKey) < 50) {
$keyCache = Base::json2array(Cache::get("KeyPair::" . $publicKey));
$publicKey = $keyCache['public_key'];
}
return self::string(self::doo()->pgpEncrypt($plaintext, $publicKey));
}
/**
* PGP解密
* @param $encryptedText
* @param $privateKey
* @param null $passphrase
* @return string
*/
public static function pgpDecrypt($encryptedText, $privateKey, $passphrase = null): string
{
if (strlen($privateKey) < 50) {
$keyCache = Base::json2array(Cache::get("KeyPair::" . $privateKey));
$privateKey = $keyCache['private_key'];
$passphrase = $keyCache['passphrase'];
}
return self::string(self::doo()->pgpDecrypt($encryptedText, $privateKey, $passphrase));
}
/**
* PGP加密API
* @param $plaintext
* @param $publicKey
* @return string
*/
public static function pgpEncryptApi($plaintext, $publicKey): string
{
$content = Base::array2json($plaintext);
$content = self::pgpEncrypt($content, $publicKey);
return preg_replace("/\s*-----(BEGIN|END) PGP MESSAGE-----\s*/i", "", $content);
}
/**
* PGP解密API
* @param $encryptedText
* @param null $privateKey
* @param null $passphrase
* @return array
*/
public static function pgpDecryptApi($encryptedText, $privateKey, $passphrase = null): array
{
$content = "-----BEGIN PGP MESSAGE-----\n\n" . $encryptedText . "\n-----END PGP MESSAGE-----";
$content = self::pgpDecrypt($content, $privateKey, $passphrase);
return Base::json2array($content);
}
/**
* 解析PGP参数
* @param $string
* @return string[]
*/
public static function pgpParseStr($string): array
{
$array = [
'encrypt_type' => '',
'encrypt_id' => '',
'client_type' => '',
'client_key' => '',
];
$string = str_replace(";", "&", $string);
parse_str($string, $params);
foreach ($params as $key => $value) {
$key = strtolower(trim($key));
if ($key) {
$array[$key] = trim($value);
}
}
if ($array['client_type'] === 'pgp' && $array['client_key']) {
$array['client_key'] = str_replace(["-", "_", "$"], ["+", "/", "\n"], $array['client_key']);
$array['client_key'] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n" . $array['client_key'] . "\n-----END PGP PUBLIC KEY BLOCK-----";
}
return $array;
}
} }

View File

@ -13,6 +13,8 @@
"ext-gd": "*", "ext-gd": "*",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-gnupg": "*",
"ext-openssl": "*",
"ext-simplexml": "*", "ext-simplexml": "*",
"directorytree/ldaprecord-laravel": "^2.7", "directorytree/ldaprecord-laravel": "^2.7",
"fideloper/proxy": "^4.4.1", "fideloper/proxy": "^4.4.1",

View File

@ -3,8 +3,12 @@ version: '3'
services: services:
php: php:
container_name: "dootask-php-${APP_ID}" container_name: "dootask-php-${APP_ID}"
image: "kuaifan/php:swoole-8.0.rc7" image: "kuaifan/php:swoole-8.0.rc9"
shm_size: "1024m" shm_size: "2gb"
ulimits:
core:
soft: 0
hard: 0
volumes: volumes:
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf - ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf
- ./docker/php/php.conf:/etc/supervisor/conf.d/php.conf - ./docker/php/php.conf:/etc/supervisor/conf.d/php.conf

View File

@ -12,6 +12,7 @@
<!--style--> <!--style-->
<link rel="stylesheet" type="text/css" href="./css/iview.css"> <link rel="stylesheet" type="text/css" href="./css/iview.css">
<link rel="stylesheet" type="text/css" href="./css/loading.css"> <link rel="stylesheet" type="text/css" href="./css/loading.css">
<script src="./js/jsencrypt.min.js"></script>
<script src="./js/scroll-into-view.min.js"></script> <script src="./js/scroll-into-view.min.js"></script>
<script src="./config.js"></script> <script src="./config.js"></script>
</head> </head>

View File

@ -1,6 +1,6 @@
{ {
"name": "DooTask", "name": "DooTask",
"version": "0.25.42", "version": "0.25.48",
"description": "DooTask is task management system.", "description": "DooTask is task management system.",
"scripts": { "scripts": {
"start": "./cmd dev", "start": "./cmd dev",
@ -23,12 +23,13 @@
"axios": "^0.24.0", "axios": "^0.24.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.7.2", "css-loader": "^6.7.2",
"dexie": "^3.2.3",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-ui": "git+https://github.com/kuaifan/element.git#master", "element-ui": "git+https://github.com/kuaifan/element.git#master",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"inquirer": "^8.2.0", "inquirer": "^8.2.0",
"internal-ip": "^6.2.0", "internal-ip": "^6.2.0",
"jquery": "^3.6.1", "jquery": "^3.6.4",
"jspdf": "^2.5.1", "jspdf": "^2.5.1",
"le5le-store": "^1.0.7", "le5le-store": "^1.0.7",
"less": "^4.1.2", "less": "^4.1.2",
@ -38,6 +39,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"notification-koro1": "^1.1.1", "notification-koro1": "^1.1.1",
"openpgp": "git+https://github.com/kuaifan/openpgpjs.git#base64",
"photoswipe": "^5.2.8", "photoswipe": "^5.2.8",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"quill": "^1.3.7", "quill": "^1.3.7",

File diff suppressed because one or more lines are too long

View File

@ -49,6 +49,7 @@ input[type="date"] {
src: url('./glyphicons-halflings-regular.eot'); src: url('./glyphicons-halflings-regular.eot');
src: url('./glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), src: url('./glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
url('./glyphicons-halflings-regular.woff') format('woff'), url('./glyphicons-halflings-regular.woff') format('woff'),
url('./glyphicons-halflings-regular.woff2') format('woff2'),
url('./glyphicons-halflings-regular.ttf') format('truetype'), url('./glyphicons-halflings-regular.ttf') format('truetype'),
url('./glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg'); url('./glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
} }

View File

@ -5,13 +5,13 @@
<meta name="description" content="APP接口文档"> <meta name="description" content="APP接口文档">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="assets/bootstrap.min.css" rel="stylesheet" media="screen"> <link href="assets/bootstrap.min.css?v=1679925462953" rel="stylesheet" media="screen">
<link href="assets/prism.css" rel="stylesheet" /> <link href="assets/prism.css?v=1679925462953" rel="stylesheet" />
<link href="assets/main.css" rel="stylesheet" media="screen, print"> <link href="assets/main.css?v=1679925462953" rel="stylesheet" media="screen, print">
<link href="assets/favicon.ico" rel="icon" type="image/x-icon"> <link href="assets/favicon.ico?v=1679925462953" rel="icon" type="image/x-icon">
<link href="assets/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"> <link href="assets/apple-touch-icon.png?v=1679925462953" rel="apple-touch-icon" sizes="180x180">
<link href="assets/favicon-32x32.png" rel="icon" type="image/png" sizes="32x32"> <link href="assets/favicon-32x32.png?v=1679925462953" rel="icon" type="image/png" sizes="32x32">
<link href="assets/favicon-16x16.png"rel="icon" type="image/png" sizes="16x16"> <link href="assets/favicon-16x16.png?v=1679925462953" rel="icon" type="image/png" sizes="16x16">
</head> </head>
<body class="container-fluid"> <body class="container-fluid">
@ -306,7 +306,7 @@
{{#if optional}} {{#if optional}}
<span class="label optional">{{__ "optional"}}</span> <span class="label optional">{{__ "optional"}}</span>
{{else}} {{else}}
{{#if ../template.showRequiredLabels}} {{#if ../../template.showRequiredLabels}}
<span class="label required">{{__ "required"}}</span> <span class="label required">{{__ "required"}}</span>
{{/if}} {{/if}}
{{/if}}</td> {{/if}}</td>
@ -928,6 +928,6 @@
</div> </div>
</div> </div>
<script src="assets/main.bundle.js"></script> <script src="assets/main.bundle.js?v=1679925462953"></script>
</body> </body>
</html> </html>

2
public/js/jsencrypt.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -212,7 +212,7 @@ store.dispatch("init").then(action => {
$A.Notice = app.$Notice; $A.Notice = app.$Notice;
$A.Modal = app.$Modal; $A.Modal = app.$Modal;
if (action === "clearCacheSuccess") { if (action === "handleClearCache") {
$A.messageSuccess("清除成功"); $A.messageSuccess("清除成功");
} }
}) })

View File

@ -36,13 +36,26 @@ const localforage = require("localforage");
* 是否在数组里 * 是否在数组里
* @param key * @param key
* @param array * @param array
* @param regular
* @returns {boolean|*} * @returns {boolean|*}
*/ */
inArray(key, array) { inArray(key, array, regular = false) {
if (!this.isArray(array)) { if (!this.isArray(array)) {
return false; return false;
} }
return array.includes(key); if (regular) {
return !!array.find(item => {
if (item && item.indexOf("*")) {
const rege = new RegExp("^" + item.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*') + "$", "g")
if (rege.test(key)) {
return true
}
}
return item == key
});
} else {
return array.includes(key);
}
}, },
/** /**

View File

@ -514,23 +514,23 @@ export default {
this.invite = $A.trim(this.invite) this.invite = $A.trim(this.invite)
// //
if (!$A.isEmail(this.email)) { if (!$A.isEmail(this.email)) {
$A.messageWarning("请输入正确的邮箱地址"); $A.messageWarning("请输入正确的邮箱地址")
this.$refs.email.focus(); this.$refs.email.focus()
return; return
} }
if (!this.password) { if (!this.password) {
$A.messageWarning("请输入密码"); $A.messageWarning("请输入密码")
this.$refs.password.focus(); this.$refs.password.focus()
return; return
} }
if (this.loginType == 'reg') { if (this.loginType == 'reg') {
if (this.password != this.password2) { if (this.password != this.password2) {
$A.messageWarning("确认密码输入不一致"); $A.messageWarning("确认密码输入不一致")
this.$refs.password2.focus(); this.$refs.password2.focus()
return; return
} }
} }
this.loadIng++; this.loadIng++
this.$store.dispatch("call", { this.$store.dispatch("call", {
url: 'users/login', url: 'users/login',
data: { data: {
@ -542,35 +542,39 @@ export default {
}, },
}).then(({data}) => { }).then(({data}) => {
$A.IDBSave("cacheLoginEmail", this.email) $A.IDBSave("cacheLoginEmail", this.email)
this.codeNeed = false; this.codeNeed = false
this.$store.dispatch("handleClearCache", data).then(this.goNext); this.$store.dispatch("handleClearCache", data).then(this.goNext)
}).catch(({data, msg}) => { }).catch(({data, msg}) => {
if (data.code === 'email') { if (data.code === 'email') {
this.loginType = 'login'; this.loginType = 'login'
$A.modalWarning(msg); $A.modalWarning(msg)
} else { } else {
$A.modalError(msg); $A.modalError({
content: msg,
onOk: _ => {
this.$refs.code.focus()
}
})
} }
if (data.code === 'need') { if (data.code === 'need') {
this.reCode(); this.reCode()
this.codeNeed = true; this.codeNeed = true
this.$refs.code.focus();
} }
}).finally(_ => { }).finally(_ => {
this.loadIng--; this.loadIng--
}); })
}) })
}, },
goNext() { goNext() {
this.loginJump = true; this.loginJump = true
const fromUrl = decodeURIComponent($A.getObject(this.$route.query, 'from')); const fromUrl = decodeURIComponent($A.getObject(this.$route.query, 'from'))
if (fromUrl) { if (fromUrl) {
$A.IDBSet("clearCache", "login").then(_ => { $A.IDBSet("clearCache", "login").then(_ => {
window.location.replace(fromUrl); window.location.replace(fromUrl)
}) })
} else { } else {
this.goForward({name: 'manage-dashboard'}, true); this.goForward({name: 'manage-dashboard'}, true)
} }
}, },

View File

@ -742,8 +742,7 @@ export default {
Store.set('updateNotification', null); Store.set('updateNotification', null);
return; return;
case 'clearCache': case 'clearCache':
this.$store.dispatch("handleClearCache", null).then(async () => { $A.IDBSet("clearCache", "handle").then(_ => {
await $A.IDBSet("clearCache", "handle")
$A.reloadUrl() $A.reloadUrl()
}); });
return; return;

View File

@ -166,7 +166,7 @@
@on-emoji="onEmoji" @on-emoji="onEmoji"
@on-show-emoji-user="onShowEmojiUser"> @on-show-emoji-user="onShowEmojiUser">
<template #header> <template #header>
<div v-if="(allMsgs.length === 0 && loadMsg) || prevId > 0" class="dialog-item loading"> <div v-if="(allMsgs.length === 0 && loadIng) || prevId > 0" class="dialog-item loading">
<div v-if="scrollOffset < 100" class="dialog-wrapper-loading"></div> <div v-if="scrollOffset < 100" class="dialog-wrapper-loading"></div>
</div> </div>
<div v-else-if="allMsgs.length === 0" class="dialog-item nothing">{{$L('暂无消息')}}</div> <div v-else-if="allMsgs.length === 0" class="dialog-item nothing">{{$L('暂无消息')}}</div>
@ -537,6 +537,7 @@ export default {
msgText: '', msgText: '',
msgNew: 0, msgNew: 0,
msgType: '', msgType: '',
loadIng: 0,
allMsgs: [], allMsgs: [],
tempMsgs: [], tempMsgs: [],
@ -936,6 +937,19 @@ export default {
immediate: true immediate: true
}, },
loadMsg: {
handler(load) {
if (load) {
this.loadIng++
} else {
setTimeout(_ => {
this.loadIng--
}, 300)
}
},
immediate: true
},
msgType() { msgType() {
this.getMsgs({ this.getMsgs({
dialog_id: this.dialogId, dialog_id: this.dialogId,

View File

@ -138,8 +138,7 @@ export default {
toggleRoute(path) { toggleRoute(path) {
switch (path) { switch (path) {
case 'clearCache': case 'clearCache':
this.$store.dispatch("handleClearCache", null).then(async () => { $A.IDBSet("clearCache", "handle").then(_ => {
await $A.IDBSet("clearCache", "handle")
$A.reloadUrl() $A.reloadUrl()
}); });
break; break;

View File

@ -1,6 +1,7 @@
import {Store} from 'le5le-store'; import {Store} from 'le5le-store';
import * as openpgp from 'openpgp/lightweight';
import {languageType} from "../language"; import {languageType} from "../language";
import {$callData} from './utils' import {$callData, $urlSafe} from './utils'
export default { export default {
/** /**
@ -26,6 +27,7 @@ export default {
} }
// 读取缓存 // 读取缓存
state.clientId = await $A.IDBString("clientId")
state.cacheServerUrl = await $A.IDBString("cacheServerUrl") state.cacheServerUrl = await $A.IDBString("cacheServerUrl")
state.cacheUserBasic = await $A.IDBArray("cacheUserBasic") state.cacheUserBasic = await $A.IDBArray("cacheUserBasic")
state.cacheDialogs = (await $A.IDBArray("cacheDialogs")).map(item => Object.assign(item, {loading: false, extra_draft_has: item.extra_draft_content ? 1 : 0})) state.cacheDialogs = (await $A.IDBArray("cacheDialogs")).map(item => Object.assign(item, {loading: false, extra_draft_has: item.extra_draft_content ? 1 : 0}))
@ -40,13 +42,19 @@ export default {
state.callAt = await $A.IDBArray("callAt") state.callAt = await $A.IDBArray("callAt")
state.cacheEmojis = await $A.IDBArray("cacheEmojis") state.cacheEmojis = await $A.IDBArray("cacheEmojis")
// 客户端ID
if (!state.clientId) {
state.clientId = $A.randomString(6)
await $A.IDBSet("clientId", state.clientId)
}
// 清理缓存 // 清理缓存
const clearCache = await $A.IDBString("clearCache") const clearCache = await $A.IDBString("clearCache")
if (clearCache) { if (clearCache) {
await $A.IDBRemove("clearCache") await $A.IDBRemove("clearCache")
await $A.IDBSet("callAt", state.callAt = []) await $A.IDBSet("callAt", state.callAt = [])
if (clearCache === "handle") { if (clearCache === "handle") {
action = "clearCacheSuccess" await dispatch(action = "handleClearCache")
} }
} }
@ -77,7 +85,15 @@ export default {
} }
state.themeIsDark = $A.dark.isDarkEnabled() state.themeIsDark = $A.dark.isDarkEnabled()
// dispatch("call", {
url: "users/key/client",
data: {client_id: state.clientId},
encrypt: false,
}).then(({data}) => {
state.apiKeyData = data;
})
// 加载语言包
$A.loadScriptS([ $A.loadScriptS([
`language/web/key.js`, `language/web/key.js`,
`language/web/${languageType}.js`, `language/web/${languageType}.js`,
@ -91,7 +107,7 @@ export default {
* 访问接口 * 访问接口
* @param state * @param state
* @param dispatch * @param dispatch
* @param params // {url,data,method,timeout,header,spinner,websocket, before,complete,success,error,after} * @param params // {url,data,method,timeout,header,spinner,websocket,encrypt, before,complete,success,error,after}
* @returns {Promise<unknown>} * @returns {Promise<unknown>}
*/ */
call({state, dispatch}, params) { call({state, dispatch}, params) {
@ -107,34 +123,68 @@ export default {
if ($A.isJson(params.header)) { if ($A.isJson(params.header)) {
params.header = Object.assign(header, params.header) params.header = Object.assign(header, params.header)
} else { } else {
params.header = header; params.header = header
} }
params.url = $A.apiUrl(params.url); if (params.encrypt === undefined && $A.inArray(params.url, [
params.data = $A.date2string(params.data); 'users/login',
'users/editpass',
'users/operation',
'users/delete/account',
'dialog/msg/*',
], true)) {
params.encrypt = true
}
params.url = $A.apiUrl(params.url)
params.data = $A.date2string(params.data)
// //
const cloneParams = $A.cloneJSON(params); const cloneParams = $A.cloneJSON(params)
return new Promise(function (resolve, reject) { return new Promise(async (resolve, reject) => {
// 加密传输
const encrypt = []
if (params.encrypt === true) {
// 有数据才加密
if (params.data) {
// PGP加密
if (state.apiKeyData.type === 'pgp') {
encrypt.push(`encrypt_type=${state.apiKeyData.type};encrypt_id=${state.apiKeyData.id}`)
params.method = "post" // 加密传输时强制使用post
params.data = {encrypted: await dispatch("pgpEncryptApi", params.data)}
}
}
encrypt.push("client_type=pgp;client_key=" + $urlSafe((await dispatch("pgpGetLocalKey")).publicKeyB64))
}
if (encrypt.length > 0) {
params.header.encrypt = encrypt.join(";")
}
// 数据转换
if (params.method === "post") {
params.data = JSON.stringify(params.data)
}
// Spinner
if (params.spinner === true || (typeof params.spinner === "number" && params.spinner > 0)) { if (params.spinner === true || (typeof params.spinner === "number" && params.spinner > 0)) {
const {before, complete} = params; const {before, complete} = params
params.before = () => { params.before = () => {
dispatch("showSpinner", typeof params.spinner === "number" ? params.spinner : 0) dispatch("showSpinner", typeof params.spinner === "number" ? params.spinner : 0)
typeof before === "function" && before() typeof before === "function" && before()
}; }
// //
params.complete = () => { params.complete = () => {
dispatch("hiddenSpinner") dispatch("hiddenSpinner")
typeof complete === "function" && complete() typeof complete === "function" && complete()
};
}
//
params.success = (result, status, xhr) => {
state.ajaxNetworkException = false;
if (!$A.isJson(result)) {
console.log(result, status, xhr);
reject({ret: -1, data: {}, msg: "Return error"})
return;
} }
const {ret, data, msg} = result; }
// 请求回调
params.success = async (result, status, xhr) => {
state.ajaxNetworkException = false
if (!$A.isJson(result)) {
console.log(result, status, xhr)
reject({ret: -1, data: {}, msg: "Return error"})
return
}
if (params.encrypt === true && result.encrypted) {
result = await dispatch("pgpDecryptApi", result.encrypted)
}
const {ret, data, msg} = result
if (ret === -1) { if (ret === -1) {
state.userId = 0 state.userId = 0
if (params.skipAuthError !== true) { if (params.skipAuthError !== true) {
@ -144,109 +194,110 @@ export default {
onOk: () => { onOk: () => {
dispatch("logout") dispatch("logout")
} }
}); })
reject(result) reject(result)
return; return
} }
} }
if (ret === -2 && params.checkNick !== false) { if (ret === -2 && params.checkNick !== false) {
// 需要昵称 // 需要昵称
dispatch("userEditInput", 'nickname').then(() => { dispatch("userEditInput", 'nickname').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject); dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => { }).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置昵称!')}) reject({ret: -1, data, msg: err || $A.L('请设置昵称!')})
}); })
return; return
} }
if (ret === -3 && params.checkTel !== false) { if (ret === -3 && params.checkTel !== false) {
// 需要联系电话 // 需要联系电话
dispatch("userEditInput", 'tel').then(() => { dispatch("userEditInput", 'tel').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject); dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => { }).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置联系电话!')}) reject({ret: -1, data, msg: err || $A.L('请设置联系电话!')})
}); })
return; return
} }
if (ret === 1) { if (ret === 1) {
resolve({data, msg}); resolve({data, msg})
} else { } else {
reject({ret, data, msg: msg || "Unknown error"}) reject({ret, data, msg: msg || "Unknown error"})
// //
if (ret === -4001) { if (ret === -4001) {
dispatch("forgetProject", data.project_id); dispatch("forgetProject", data.project_id)
} else if (ret === -4002) { } else if (ret === -4002) {
dispatch("forgetTask", data.task_id); dispatch("forgetTask", data.task_id)
} else if (ret === -4003) { } else if (ret === -4003) {
dispatch("forgetDialog", data.dialog_id); dispatch("forgetDialog", data.dialog_id)
} }
} }
}; }
params.error = (xhr, status) => { params.error = (xhr, status) => {
const networkException = window.navigator.onLine === false || (status === 0 && xhr.readyState === 4); const networkException = window.navigator.onLine === false || (status === 0 && xhr.readyState === 4)
if (params.checkNetwork !== false) { if (params.checkNetwork !== false) {
state.ajaxNetworkException = networkException; state.ajaxNetworkException = networkException
} }
if (networkException) { if (networkException) {
reject({ret: -1001, data: {}, msg: "Network exception"}) reject({ret: -1001, data: {}, msg: "Network exception"})
} else { } else {
reject({ret: -1, data: {}, msg: "System error"}) reject({ret: -1, data: {}, msg: "System error"})
} }
}; }
// // WebSocket
if (params.websocket === true || params.ws === true) { if (params.websocket === true) {
const apiWebsocket = $A.randomString(16); const apiWebsocket = $A.randomString(16)
const apiTimeout = setTimeout(() => { const apiTimeout = setTimeout(() => {
const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket); const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket)
if (WListener) { if (WListener) {
WListener.complete(); WListener.complete()
WListener.error("timeout"); WListener.error("timeout")
WListener.after(); WListener.after()
} }
state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket); state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket)
}, params.timeout || 30000); }, params.timeout || 30000)
state.ajaxWsListener.push({ state.ajaxWsListener.push({
apiWebsocket: apiWebsocket, apiWebsocket: apiWebsocket,
complete: typeof params.complete === "function" ? params.complete : () => { }, complete: typeof params.complete === "function" ? params.complete : () => { },
success: typeof params.success === "function" ? params.success : () => { }, success: typeof params.success === "function" ? params.success : () => { },
error: typeof params.error === "function" ? params.error : () => { }, error: typeof params.error === "function" ? params.error : () => { },
after: typeof params.after === "function" ? params.after : () => { }, after: typeof params.after === "function" ? params.after : () => { },
}); })
// //
params.complete = () => { }; params.complete = () => { }
params.success = () => { }; params.success = () => { }
params.error = () => { }; params.error = () => { }
params.after = () => { }; params.after = () => { }
params.header['Api-Websocket'] = apiWebsocket; params.header['Api-Websocket'] = apiWebsocket
// //
if (state.ajaxWsReady === false) { if (state.ajaxWsReady === false) {
state.ajaxWsReady = true; state.ajaxWsReady = true
dispatch("websocketMsgListener", { dispatch("websocketMsgListener", {
name: "apiWebsocket", name: "apiWebsocket",
callback: (msg) => { callback: (msg) => {
switch (msg.type) { switch (msg.type) {
case 'apiWebsocket': case 'apiWebsocket':
clearTimeout(apiTimeout); clearTimeout(apiTimeout)
const apiWebsocket = msg.apiWebsocket; const apiWebsocket = msg.apiWebsocket
const apiSuccess = msg.apiSuccess; const apiSuccess = msg.apiSuccess
const apiResult = msg.data; const apiResult = msg.data
const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket); const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket)
if (WListener) { if (WListener) {
WListener.complete(); WListener.complete()
if (apiSuccess) { if (apiSuccess) {
WListener.success(apiResult); WListener.success(apiResult)
} else { } else {
WListener.error(apiResult); WListener.error(apiResult)
} }
WListener.after(); WListener.after()
} }
state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket); state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket)
break; break
} }
} }
}); })
} }
} }
$A.ajaxc(params); //
$A.ajaxc(params)
}) })
}, },
@ -668,6 +719,7 @@ export default {
const cacheLoginEmail = await $A.IDBString("cacheLoginEmail"); const cacheLoginEmail = await $A.IDBString("cacheLoginEmail");
const cacheFileSort = await $A.IDBJson("cacheFileSort"); const cacheFileSort = await $A.IDBJson("cacheFileSort");
await $A.IDBClear(); await $A.IDBClear();
await $A.IDBSet("clientId", state.clientId);
await $A.IDBSet("cacheServerUrl", state.cacheServerUrl); await $A.IDBSet("cacheServerUrl", state.cacheServerUrl);
await $A.IDBSet("cacheProjectParameter", state.cacheProjectParameter); await $A.IDBSet("cacheProjectParameter", state.cacheProjectParameter);
await $A.IDBSet("cacheLoginEmail", cacheLoginEmail); await $A.IDBSet("cacheLoginEmail", cacheLoginEmail);
@ -3132,5 +3184,141 @@ export default {
state.ws.close(); state.ws.close();
state.ws = null; state.ws = null;
} }
},
/** *****************************************************************************************/
/** *************************************** pgp *********************************************/
/** *****************************************************************************************/
/**
* 创建密钥对
* @param state
* @returns {Promise<unknown>}
*/
pgpGenerate({state}) {
return new Promise(async resolve => {
const data = await openpgp.generateKey({
type: 'ecc',
curve: 'curve25519',
passphrase: state.clientId,
userIDs: [{name: 'doo', email: 'admin@admin.com'}],
})
data.publicKeyB64 = data.publicKey.replace(/\s*-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----\s*/g, '').replace(/\n+/g, '$')
resolve(data)
})
},
/**
* 获取密钥对不存在自动创建
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
pgpGetLocalKey({state, dispatch}) {
return new Promise(async resolve => {
// 已存在
if (state.localKeyPair.privateKey) {
return resolve(state.localKeyPair)
}
// 避免重复生成
while (state.localKeyLock === true) {
await new Promise(r => setTimeout(r, 100));
}
if (state.localKeyPair.privateKey) {
return resolve(state.localKeyPair)
}
// 生成密钥对
state.localKeyLock = true
state.localKeyPair = await dispatch("pgpGenerate")
state.localKeyLock = false
resolve(state.localKeyPair)
})
},
/**
* 加密
* @param state
* @param dispatch
* @param data {message:any, ?publicKey:string}
* @returns {Promise<unknown>}
*/
pgpEncrypt({state, dispatch}, data) {
return new Promise(async resolve => {
if (!$A.isJson(data)) {
data = {message: data}
}
const message = data.message || data.text
const publicKeyArmored = data.publicKey || data.key || (await dispatch("pgpGetLocalKey")).publicKey
const encryptionKeys = await openpgp.readKey({armoredKey: publicKeyArmored})
//
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({text: message}),
encryptionKeys,
})
resolve(encrypted)
})
},
/**
* 解密
* @param state
* @param dispatch
* @param data {encrypted:any, ?privateKey:string, ?passphrase:string}
* @returns {Promise<unknown>}
*/
pgpDecrypt({state, dispatch}, data) {
return new Promise(async resolve => {
if (!$A.isJson(data)) {
data = {encrypted: data}
}
const encrypted = data.encrypted || data.text
const privateKeyArmored = data.privateKey || data.key || (await dispatch("pgpGetLocalKey")).privateKey
const decryptionKeys = await openpgp.decryptKey({
privateKey: await openpgp.readPrivateKey({armoredKey: privateKeyArmored}),
passphrase: data.passphrase || state.clientId
})
//
const {data: decryptData} = await openpgp.decrypt({
message: await openpgp.readMessage({armoredMessage: encrypted}),
decryptionKeys
})
resolve(decryptData)
})
},
/**
* API加密
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
pgpEncryptApi({state, dispatch}, data) {
return new Promise(resolve => {
data = $A.jsonStringify(data)
dispatch("pgpEncrypt", {
message: data,
publicKey: state.apiKeyData.key
}).then(data => {
resolve(data.replace(/\s*-----(BEGIN|END) PGP MESSAGE-----\s*/g, ''))
})
})
},
/**
* API解密
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
pgpDecryptApi({state, dispatch}, data) {
return new Promise(resolve => {
dispatch("pgpDecrypt", {
encrypted: "-----BEGIN PGP MESSAGE-----\n\n" + data + "\n-----END PGP MESSAGE-----"
}).then(data => {
resolve($A.jsonParse(data))
})
})
} }
} }

View File

@ -1,4 +1,7 @@
export default { export default {
// 客户端ID希望不变的除非清除浏览器缓存或者卸载应用
clientId: "",
// 是否移动端(支持触摸) // 是否移动端(支持触摸)
supportTouch: "ontouchend" in document, supportTouch: "ontouchend" in document,
@ -170,4 +173,9 @@ export default {
// 表单布局 // 表单布局
formLabelPosition: $A(window).width() > 576 ? 'right' : 'top', formLabelPosition: $A(window).width() > 576 ? 'right' : 'top',
formLabelWidth: $A(window).width() > 576 ? 'auto' : '', formLabelWidth: $A(window).width() > 576 ? 'auto' : '',
// 加密相关
apiKeyData: {},
localKeyPair: {},
localKeyLock: false,
}; };

View File

@ -73,3 +73,14 @@ function __callData(key, requestData, state) {
export function $callData(key, requestData, state) { export function $callData(key, requestData, state) {
return new __callData(key, requestData, state) return new __callData(key, requestData, state)
} }
export function $urlSafe(value, encode = true) {
if (value) {
if (encode) {
value = String(value).replace(/\+/g, "-").replace(/\//g, "_")
} else {
value = String(value).replace(/-/g, "+").replace(/_/g, "/")
}
}
return value
}

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@
@endif @endif
<link rel="stylesheet" type="text/css" href="{{ asset_main('css/iview.css') }}?v={{ $version }}"> <link rel="stylesheet" type="text/css" href="{{ asset_main('css/iview.css') }}?v={{ $version }}">
<link rel="stylesheet" type="text/css" href="{{ asset_main('css/loading.css') }}?v={{ $version }}"> <link rel="stylesheet" type="text/css" href="{{ asset_main('css/loading.css') }}?v={{ $version }}">
<script src="{{ asset_main('js/jsencrypt.min.js') }}?v={{ $version }}"></script>
<script src="{{ asset_main('js/scroll-into-view.min.js') }}?v={{ $version }}"></script> <script src="{{ asset_main('js/scroll-into-view.min.js') }}?v={{ $version }}"></script>
<script> <script>
window.csrfToken = { window.csrfToken = {