From 11d30091c8f2769b17a48c2d6e8eedbe9618db95 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 1 Aug 2021 16:58:00 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20assets=20=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E7=94=A8=E4=BD=9C=E6=9C=AC=E5=9C=B0=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=EF=BC=8C=E7=A7=BB=E5=88=B0=E9=A1=B9=E7=9B=AE=E6=A0=B9?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/file.js | 2 +- app/extension/file/config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/cms/file.js b/app/api/cms/file.js index 76b2388..29533af 100644 --- a/app/api/cms/file.js +++ b/app/api/cms/file.js @@ -12,7 +12,7 @@ file.linPost('upload', '/', loginRequired, async ctx => { if (files.length < 1) { throw new ParametersException({ code: 10033 }); } - const uploader = new LocalUploader('app/assets'); + const uploader = new LocalUploader('assets'); const arr = await uploader.upload(files); ctx.json(arr); }); diff --git a/app/extension/file/config.js b/app/extension/file/config.js index b1e4b71..6e108a9 100644 --- a/app/extension/file/config.js +++ b/app/extension/file/config.js @@ -2,7 +2,7 @@ module.exports = { file: { - storeDir: 'app/assets', + storeDir: 'assets', singleLimit: 1024 * 1024 * 2, totalLimit: 1024 * 1024 * 20, nums: 10, From b340903fda123ec18034ce07e97cd184bc5350f1 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 1 Aug 2021 16:58:19 +0800 Subject: [PATCH 2/5] =?UTF-8?q?style(*):=20prettier=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- app/api/cms/user.js | 16 ++-- app/app.js | 8 +- app/config/secure.js | 2 +- app/dao/admin.js | 4 +- app/extension/socket/config.js | 4 +- app/extension/socket/socket.js | 130 +++++++++++++++++---------------- app/middleware/logger.js | 1 - app/model/user.js | 8 +- app/starter.js | 2 +- package.json | 2 +- 11 files changed, 93 insertions(+), 86 deletions(-) diff --git a/.gitignore b/.gitignore index a571d04..2487b48 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ suspect/ dist learn tokens.json -app/assets \ No newline at end of file +assets \ No newline at end of file diff --git a/app/api/cms/user.js b/app/api/cms/user.js index a672a56..1c0c270 100644 --- a/app/api/cms/user.js +++ b/app/api/cms/user.js @@ -34,12 +34,16 @@ user.linPost( const v = await new RegisterValidator().validate(ctx); await userDao.createUser(v); if (config.getItem('socket.enable')) { - const username = v.get('body.username') - ctx.websocket.broadCast(JSON.stringify({ - name: username, - content: `管理员${ctx.currentUser.getDataValue('username')}新建了一个用户${username}`, - time: new Date() - })) + const username = v.get('body.username'); + ctx.websocket.broadCast( + JSON.stringify({ + name: username, + content: `管理员${ctx.currentUser.getDataValue( + 'username' + )}新建了一个用户${username}`, + time: new Date() + }) + ); } ctx.success({ code: 11 diff --git a/app/app.js b/app/app.js index 1b6a37c..01dd524 100644 --- a/app/app.js +++ b/app/app.js @@ -5,7 +5,7 @@ import mount from 'koa-mount'; import serve from 'koa-static'; import { config, json, logging, success, jwt, Loader } from 'lin-mizar'; import { PermissionModel } from './model/permission'; -import WebSocket from './extension/socket/socket' +import WebSocket from './extension/socket/socket'; /** * 首页 @@ -64,10 +64,10 @@ function applyDefaultExtends (app) { */ function applyWebSocket (app) { if (config.getItem('socket.enable')) { - const server = new WebSocket(app) - return server.init() + const server = new WebSocket(app); + return server.init(); } - return app + return app; } /** diff --git a/app/config/secure.js b/app/config/secure.js index 81aadaa..8f38815 100644 --- a/app/config/secure.js +++ b/app/config/secure.js @@ -12,7 +12,7 @@ module.exports = { timezone: '+08:00', define: { charset: 'utf8mb4' - }, + } }, secret: '\x88W\xf09\x91\x07\x98\x89\x87\x96\xa0A\xc68\xf9\xecJJU\x17\xc5V\xbe\x8b\xef\xd7\xd8\xd3\xe6\x95*4' // 发布生产环境前,请务必修改此默认秘钥 diff --git a/app/dao/admin.js b/app/dao/admin.js index 75aace2..8c648b7 100644 --- a/app/dao/admin.js +++ b/app/dao/admin.js @@ -107,11 +107,11 @@ class AdminDao { group_id: GroupLevel.Root, user_id: id } - }) + }); if (root) { throw new Forbidden({ code: 10079 - }) + }); } let transaction; try { diff --git a/app/extension/socket/config.js b/app/extension/socket/config.js index 862971e..785db52 100644 --- a/app/extension/socket/config.js +++ b/app/extension/socket/config.js @@ -4,6 +4,6 @@ module.exports = { socket: { path: '/ws/message', enable: false, // 是否开启 websocket 模块 - intercept: false, // 是否开启 websocket 的鉴权拦截器 + intercept: false // 是否开启 websocket 的鉴权拦截器 } -}; \ No newline at end of file +}; diff --git a/app/extension/socket/socket.js b/app/extension/socket/socket.js index 7f23b06..fff3be9 100644 --- a/app/extension/socket/socket.js +++ b/app/extension/socket/socket.js @@ -1,80 +1,82 @@ -import http from 'http' -import Ws from 'ws' +import http from 'http'; +import Ws from 'ws'; import { config, jwt } from 'lin-mizar'; -import { URLSearchParams } from 'url' -import { set, get } from 'lodash' +import { URLSearchParams } from 'url'; +import { set, get } from 'lodash'; import { UserGroupModel } from '../../model/user-group'; -const USER_KEY = Symbol('user') +const USER_KEY = Symbol('user'); -const INTERCEPTORS = Symbol('WebSocket#interceptors') +const INTERCEPTORS = Symbol('WebSocket#interceptors'); -const HANDLE_CLOSE = Symbol('WebSocket#close') +const HANDLE_CLOSE = Symbol('WebSocket#close'); -const HANDLE_ERROR = Symbol('WebSocket#error') +const HANDLE_ERROR = Symbol('WebSocket#error'); class WebSocket { - constructor(app) { - this.app = app - this.wss = null - this.sessions = new Set() + constructor (app) { + this.app = app; + this.wss = null; + this.sessions = new Set(); } /** * 初始化,挂载 socket */ - init() { - const server = http.createServer(this.app.callback()) + init () { + const server = http.createServer(this.app.callback()); this.wss = new Ws.Server({ path: config.getItem('socket.path', '/ws/message'), noServer: true - }) + }); server.on('upgrade', this[INTERCEPTORS].bind(this)); - this.wss.on('connection', (socket) => { - socket.on('close', this[HANDLE_CLOSE].bind(this)) - socket.on('error', this[HANDLE_ERROR].bind(this)) - }) + this.wss.on('connection', socket => { + socket.on('close', this[HANDLE_CLOSE].bind(this)); + socket.on('error', this[HANDLE_ERROR].bind(this)); + }); - this.app.context.websocket = this - return server + this.app.context.websocket = this; + return server; } - [INTERCEPTORS](request, socket, head) { + [INTERCEPTORS] (request, socket, head) { // 是否开启 websocket 的鉴权拦截器 if (config.getItem('socket.intercept')) { - const params = new URLSearchParams(request.url.slice(request.url.indexOf('?'))) - const token = params.get('token') + const params = new URLSearchParams( + request.url.slice(request.url.indexOf('?')) + ); + const token = params.get('token'); try { - const { identity } = jwt.verifyToken(token) - this.wss.handleUpgrade(request, socket, head, (ws) => { - set(ws, USER_KEY, identity) - this.sessions.add(ws) + const { identity } = jwt.verifyToken(token); + this.wss.handleUpgrade(request, socket, head, ws => { + set(ws, USER_KEY, identity); + this.sessions.add(ws); this.wss.emit('connection', ws, request); - }) + }); } catch (error) { - console.log(error.message) - socket.destroy() + console.log(error.message); + socket.destroy(); } - return + return; } - this.wss.handleUpgrade(request, socket, head, (ws) => { - this.sessions.add(ws) + this.wss.handleUpgrade(request, socket, head, ws => { + this.sessions.add(ws); this.wss.emit('connection', ws, request); - }) + }); } - [HANDLE_CLOSE]() { + [HANDLE_CLOSE] () { for (const session of this.sessions) { if (session.readyState === Ws.CLOSED) { - this.sessions.delete(session) + this.sessions.delete(session); } } } - [HANDLE_ERROR](session, error) { - console.log(error) + [HANDLE_ERROR] (session, error) { + console.log(error); } /** @@ -83,14 +85,14 @@ class WebSocket { * @param {number} userId 用户id * @param {string} message 消息 */ - sendMessage(userId, message) { + sendMessage (userId, message) { for (const session of this.sessions) { if (session.readyState === Ws.OPEN) { - continue + continue; } if (get(session, USER_KEY) === userId) { - session.send(message) - break + session.send(message); + break; } } } @@ -100,47 +102,47 @@ class WebSocket { * * @param {WebSocket} session 当前会话 * @param {string} message 消息 - */ - sendMessageToSession(session, message) { - session.send(message) + */ + sendMessageToSession (session, message) { + session.send(message); } /** * 广播 - * - * @param {string} message 消息 + * + * @param {string} message 消息 */ - broadCast(message) { + broadCast (message) { this.sessions.forEach(session => { if (session.readyState === Ws.OPEN) { - session.send(message) + session.send(message); } - }) + }); } /** * 对某个分组广播 - * + * * @param {number} 分组id * @param {string} 消息 */ - async broadCastToGroup(groupId, message) { + async broadCastToGroup (groupId, message) { const userGroup = await UserGroupModel.findAll({ where: { group_id: groupId } - }) - const userIds = userGroup.map(v => v.getDataValue('user_id')) + }); + const userIds = userGroup.map(v => v.getDataValue('user_id')); for (const session of this.sessions) { if (session.readyState !== Ws.OPEN) { - continue + continue; } - const userId = get(session, USER_KEY) + const userId = get(session, USER_KEY); if (!userId) { - continue + continue; } if (userIds.includes(userId)) { - session.send(message) + session.send(message); } } } @@ -148,16 +150,16 @@ class WebSocket { /** * 获取所有会话 */ - getSessions() { - return this.sessions + getSessions () { + return this.sessions; } /** * 获得当前连接数 */ - getConnectionCount() { - return this.sessions.size + getConnectionCount () { + return this.sessions.size; } } -export default WebSocket \ No newline at end of file +export default WebSocket; diff --git a/app/middleware/logger.js b/app/middleware/logger.js index cd97189..71865fb 100644 --- a/app/middleware/logger.js +++ b/app/middleware/logger.js @@ -113,4 +113,3 @@ function parseTemplate (template, user, response, request) { } return template; } - diff --git a/app/model/user.js b/app/model/user.js index e2fda55..6547593 100644 --- a/app/model/user.js +++ b/app/model/user.js @@ -113,9 +113,11 @@ class User extends Model { username: this.username, nickname: this.nickname, email: this.email, - avatar: this.avatar ? `${config.getItem('siteDomain', 'http://localhost')}/assets/${ - this.avatar - }` : '' + avatar: this.avatar + ? `${config.getItem('siteDomain', 'http://localhost')}/assets/${ + this.avatar + }` + : '' }; if (has(this, 'groups')) { return { ...origin, groups: get(this, 'groups', []) }; diff --git a/app/starter.js b/app/starter.js index 6bd97f5..8f1a9df 100644 --- a/app/starter.js +++ b/app/starter.js @@ -11,7 +11,7 @@ const { config } = require('lin-mizar/lin/config'); // if (files.length < 1) { // throw new Error('未找到符合条件的文件资源'); // } -// const uploader = new LocalUploader('app/assets'); +// const uploader = new LocalUploader('assets'); // const arr = await uploader.upload(files); // }); diff --git a/package.json b/package.json index b44940c..3d27b82 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "koa-bodyparser": "^4.2.1", "koa-mount": "^4.0.0", "koa-static": "^5.0.0", - "lin-mizar": "^0.3.8", + "lin-mizar": "^0.3.9", "mysql2": "^2.1.0", "sequelize": "^5.21.13", "validator": "^13.1.1", From b54878707861aa154542e6d229bfb0eefea3967f Mon Sep 17 00:00:00 2001 From: shirmy Date: 2021年9月12日 21:07:46 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(*):=20=E6=96=B0=E5=A2=9E=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E9=AA=8C=E8=AF=81=E7=A0=81=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/user.js | 23 +++++--- app/config/code-message.js | 3 +- app/config/setting.js | 12 +++-- app/dao/user.js | 35 +++++++++++- app/lib/captcha.js | 108 +++++++++++++++++++++++++++++++++++++ app/validator/user.js | 6 ++- package.json | 3 ++ 7 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 app/lib/captcha.js diff --git a/app/api/cms/user.js b/app/api/cms/user.js index 1c0c270..4dea2aa 100644 --- a/app/api/cms/user.js +++ b/app/api/cms/user.js @@ -14,6 +14,7 @@ import { import { UserIdentityModel } from '../../model/user'; import { logger } from '../../middleware/logger'; import { UserDao } from '../../dao/user'; +import { generateCaptcha } from '../../lib/captcha'; const user = new LinRouter({ prefix: '/cms/user', @@ -53,19 +54,27 @@ user.linPost( user.linPost('userLogin', '/login', user.permission('登录'), async ctx => { const v = await new LoginValidator().validate(ctx); - const user = await UserIdentityModel.verify( - v.get('body.username'), - v.get('body.password') - ); - const { accessToken, refreshToken } = getTokens({ - id: user.user_id - }); + const { accessToken, refreshToken } = await userDao.getTokens(v, ctx); ctx.json({ access_token: accessToken, refresh_token: refreshToken }); }); +user.linPost('userCaptcha', '/captcha', async ctx => { + let tag = null; + let image = null; + + if (config.getItem('loginCaptchaEnabled', false)) { + ({ tag, image } = await generateCaptcha()); + } + + ctx.json({ + tag, + image + }); +}); + user.linPut( 'userUpdate', '/', diff --git a/app/config/code-message.js b/app/config/code-message.js index 3eaff38..07169da 100644 --- a/app/config/code-message.js +++ b/app/config/code-message.js @@ -77,6 +77,7 @@ module.exports = { 10231: '无法分配不存在的权限', 10240: '书籍已存在', 10250: '请使用正确类型的令牌', - 10251: '请使用正确作用域的令牌' + 10251: '请使用正确作用域的令牌', + 10260: '请输入正确的验证码' } }; diff --git a/app/config/setting.js b/app/config/setting.js index bdc2fe3..ab3d103 100644 --- a/app/config/setting.js +++ b/app/config/setting.js @@ -1,14 +1,14 @@ -const path = require('path'); +const path = require("path"); module.exports = { port: 5000, - siteDomain: 'http://localhost:5000', + siteDomain: "http://localhost:5000", countDefault: 10, pageDefault: 0, - apiDir: 'app/api', + apiDir: "app/api", accessExp: 60 * 60, // 1h 单位秒 // 指定工作目录,默认为 process.cwd() 路径 - baseDir: path.resolve(__dirname, '../../'), + baseDir: path.resolve(__dirname, "../../"), // debug 模式 debug: true, // refreshExp 设置refresh_token的过期时间,默认一个月 @@ -24,5 +24,7 @@ module.exports = { // // other config // limit: 2 // }, - } + }, + // 是否开启登录验证码 + loginCaptchaEnabled: false, }; diff --git a/app/dao/user.js b/app/dao/user.js index 9f4c474..2683ab1 100644 --- a/app/dao/user.js +++ b/app/dao/user.js @@ -1,4 +1,11 @@ -import { RepeatException, generate, NotFound, Forbidden } from 'lin-mizar'; +import { + RepeatException, + generate, + NotFound, + Forbidden, + config, + getTokens +} from 'lin-mizar'; import { UserModel, UserIdentityModel } from '../model/user'; import { UserGroupModel } from '../model/user-group'; import { GroupPermissionModel } from '../model/group-permission'; @@ -9,6 +16,7 @@ import sequelize from '../lib/db'; import { MountType, GroupLevel, IdentityType } from '../lib/type'; import { Op } from 'sequelize'; import { set, has, uniq } from 'lodash'; +import { verifyCaptcha } from '../lib/captcha'; class UserDao { async createUser (v) { @@ -50,6 +58,31 @@ class UserDao { await this.registerUser(v); } + async getTokens (v, ctx) { + if (config.getItem('loginCaptchaEnabled', false)) { + const tag = ctx.req.headers.tag; + const captcha = v.get('body.captcha'); + + if (!verifyCaptcha(captcha, tag)) { + throw new Forbidden({ + code: 10260 + }); + } + } + const user = await UserIdentityModel.verify( + v.get('body.username'), + v.get('body.password') + ); + const { accessToken, refreshToken } = getTokens({ + id: user.user_id + }); + + return { + accessToken, + refreshToken + }; + } + async updateUser (ctx, v) { const user = ctx.currentUser; if (v.get('body.username') && user.username !== v.get('body.username')) { diff --git a/app/lib/captcha.js b/app/lib/captcha.js new file mode 100644 index 0000000..240feea --- /dev/null +++ b/app/lib/captcha.js @@ -0,0 +1,108 @@ +import sharp from "sharp"; +import svgCaptcha from "svg-captcha"; +import { + createCipheriv, + createDecipheriv, + randomBytes, + createHash, +} from "crypto"; +import { config } from "lin-mizar"; + +const iv = Buffer.from(randomBytes(8)).toString("hex"); +const secret = config.getItem("secret"); +const key = createHash("sha256") + .update(String(secret)) + .digest("base64") + .substr(0, 32); + +/** + * 加密 + * + * @param {string} value 需要加密的信息 + * @returns 加密后的值 + */ +function aesEncrypt(value) { + const cipher = createCipheriv("aes-256-ctr", key, iv); + let encrypted = cipher.update(value, "utf8", "hex"); + encrypted += cipher.final("hex"); + return encrypted; +} + +/** + * 解密 + * + * @param {string} encrypted 需要解密的信息 + * @returns 解密后的值 + */ +function aesDecrypt(encrypted) { + const cipher = createDecipheriv("aes-256-ctr", key, iv); + let decrypted = cipher.update(encrypted, "hex", "utf8"); + decrypted += cipher.final("utf8"); + return decrypted; +} + +/** + * 给 tag 加密 + */ +function getTag(captcha) { + const date = new Date(); + // 5 分钟后过期 + date.setMinutes(date.getMinutes() + 5); + const info = { + captcha, + expired: date.getTime(), + }; + + return aesEncrypt(JSON.stringify(info)); +} + +/** + * 校验验证码是否正确 + */ +function verifyCaptcha(loginCaptcha, tag) { + if (!loginCaptcha || !tag) { + return false; + } + const decrypted = aesDecrypt(tag); + try { + const { captcha, expired } = JSON.parse(decrypted); + // 大小写不敏感 + if ( + loginCaptcha.toLowerCase() !== captcha.toLowerCase() || + new Date().getTime()> expired + ) { + return false; + } + } catch (error) { + return false; + } + return true; +} + +/** + * 生成验证码图片及对称加密用到的 tag + */ +async function generateCaptcha() { + const captcha = svgCaptcha.create({ + size: 4, // 验证码长度 + fontSize: 45, // 验证码字号 + noise: Math.floor(Math.random() * 5), // 干扰线条数目 + width: 80, // 宽度 + height: 40, // 高度 + color: true, // 验证码字符是否有颜色,默认是没有,但是如果设置了背景颜色,那么默认就是有字符颜色 + background: "#fff", // 背景色 + }); + + const { data, text } = captcha; + const str = await sharp(Buffer.from(data)) + .png() + .toBuffer(); + const image = "data:image/jpg;base64," + str.toString("base64"); + + return { + image, + tag: getTag(text), + }; +} + +export { generateCaptcha, verifyCaptcha }; diff --git a/app/validator/user.js b/app/validator/user.js index f5555d0..0ed0d86 100644 --- a/app/validator/user.js +++ b/app/validator/user.js @@ -1,4 +1,4 @@ -import { LinValidator, Rule } from 'lin-mizar'; +import { config, LinValidator, Rule } from 'lin-mizar'; import { isOptional } from '../lib/util'; import validator from 'validator'; @@ -60,6 +60,10 @@ class LoginValidator extends LinValidator { super(); this.username = new Rule('isNotEmpty', '用户名不可为空'); this.password = new Rule('isNotEmpty', '密码不可为空'); + + if (config.getItem('loginCaptchaEnabled', false)) { + this.captcha = new Rule('isNotEmpty', '验证码不能为空'); + } } } diff --git a/package.json b/package.json index 3d27b82..dd14538 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ }, "dependencies": { "@koa/cors": "^2.2.3", + "crypto": "^1.0.1", "koa": "^2.7.0", "koa-bodyparser": "^4.2.1", "koa-mount": "^4.0.0", @@ -55,6 +56,8 @@ "lin-mizar": "^0.3.9", "mysql2": "^2.1.0", "sequelize": "^5.21.13", + "sharp": "^0.29.0", + "svg-captcha": "^1.4.0", "validator": "^13.1.1", "ws": "^7.4.4" } From bbea1c25edbaf7d7ca9818b38be4b23ea8cbaa2e Mon Sep 17 00:00:00 2001 From: wang-ev Date: Mon, 8 Nov 2021 09:30:01 +0800 Subject: [PATCH 4/5] feat: add captcha functionality --- README.md | 5 ++++- app/config/setting.js | 10 ++++----- app/lib/captcha.js | 48 +++++++++++++++++++++---------------------- jest.config.js | 7 +++---- package.json | 2 +- 5 files changed, 37 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 03c1a0b..e611230 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,11 @@ QQ 群号:643205479 / 814597236 ## 版本日志 -最新版本 `0.3.11` +最新版本 `0.3.12` +### 0.3.12 + +1. `A` 新增验证码功能,默认关闭验证码 ### 0.3.11 1. `F` 修复消息中心 API 调用拼写错误 diff --git a/app/config/setting.js b/app/config/setting.js index ab3d103..42c9892 100644 --- a/app/config/setting.js +++ b/app/config/setting.js @@ -1,14 +1,14 @@ -const path = require("path"); +const path = require('path'); module.exports = { port: 5000, - siteDomain: "http://localhost:5000", + siteDomain: 'http://localhost:5000', countDefault: 10, pageDefault: 0, - apiDir: "app/api", + apiDir: 'app/api', accessExp: 60 * 60, // 1h 单位秒 // 指定工作目录,默认为 process.cwd() 路径 - baseDir: path.resolve(__dirname, "../../"), + baseDir: path.resolve(__dirname, '../../'), // debug 模式 debug: true, // refreshExp 设置refresh_token的过期时间,默认一个月 @@ -26,5 +26,5 @@ module.exports = { // }, }, // 是否开启登录验证码 - loginCaptchaEnabled: false, + loginCaptchaEnabled: false }; diff --git a/app/lib/captcha.js b/app/lib/captcha.js index 240feea..046a210 100644 --- a/app/lib/captcha.js +++ b/app/lib/captcha.js @@ -1,18 +1,18 @@ -import sharp from "sharp"; -import svgCaptcha from "svg-captcha"; +import sharp from 'sharp'; +import svgCaptcha from 'svg-captcha'; import { createCipheriv, createDecipheriv, randomBytes, - createHash, -} from "crypto"; -import { config } from "lin-mizar"; + createHash +} from 'crypto'; +import { config } from 'lin-mizar'; -const iv = Buffer.from(randomBytes(8)).toString("hex"); -const secret = config.getItem("secret"); -const key = createHash("sha256") +const iv = Buffer.from(randomBytes(8)).toString('hex'); +const secret = config.getItem('secret'); +const key = createHash('sha256') .update(String(secret)) - .digest("base64") + .digest('base64') .substr(0, 32); /** @@ -21,10 +21,10 @@ const key = createHash("sha256") * @param {string} value 需要加密的信息 * @returns 加密后的值 */ -function aesEncrypt(value) { - const cipher = createCipheriv("aes-256-ctr", key, iv); - let encrypted = cipher.update(value, "utf8", "hex"); - encrypted += cipher.final("hex"); +function aesEncrypt (value) { + const cipher = createCipheriv('aes-256-ctr', key, iv); + let encrypted = cipher.update(value, 'utf8', 'hex'); + encrypted += cipher.final('hex'); return encrypted; } @@ -34,23 +34,23 @@ function aesEncrypt(value) { * @param {string} encrypted 需要解密的信息 * @returns 解密后的值 */ -function aesDecrypt(encrypted) { - const cipher = createDecipheriv("aes-256-ctr", key, iv); - let decrypted = cipher.update(encrypted, "hex", "utf8"); - decrypted += cipher.final("utf8"); +function aesDecrypt (encrypted) { + const cipher = createDecipheriv('aes-256-ctr', key, iv); + let decrypted = cipher.update(encrypted, 'hex', 'utf8'); + decrypted += cipher.final('utf8'); return decrypted; } /** * 给 tag 加密 */ -function getTag(captcha) { +function getTag (captcha) { const date = new Date(); // 5 分钟后过期 date.setMinutes(date.getMinutes() + 5); const info = { captcha, - expired: date.getTime(), + expired: date.getTime() }; return aesEncrypt(JSON.stringify(info)); @@ -59,7 +59,7 @@ function getTag(captcha) { /** * 校验验证码是否正确 */ -function verifyCaptcha(loginCaptcha, tag) { +function verifyCaptcha (loginCaptcha, tag) { if (!loginCaptcha || !tag) { return false; } @@ -82,7 +82,7 @@ function verifyCaptcha(loginCaptcha, tag) { /** * 生成验证码图片及对称加密用到的 tag */ -async function generateCaptcha() { +async function generateCaptcha () { const captcha = svgCaptcha.create({ size: 4, // 验证码长度 fontSize: 45, // 验证码字号 @@ -90,18 +90,18 @@ async function generateCaptcha() { width: 80, // 宽度 height: 40, // 高度 color: true, // 验证码字符是否有颜色,默认是没有,但是如果设置了背景颜色,那么默认就是有字符颜色 - background: "#fff", // 背景色 + background: '#fff' // 背景色 }); const { data, text } = captcha; const str = await sharp(Buffer.from(data)) .png() .toBuffer(); - const image = "data:image/jpg;base64," + str.toString("base64"); + const image = 'data:image/jpg;base64,' + str.toString('base64'); return { image, - tag: getTag(text), + tag: getTag(text) }; } diff --git a/jest.config.js b/jest.config.js index 611c7b0..d2caf42 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,14 +2,13 @@ // https://jestjs.io/docs/en/configuration.html module.exports = { - coverageDirectory: 'coverage', testEnvironment: 'node', + coverageDirectory: 'coverage', testPathIgnorePatterns: ['/node_modules/'], testMatch: [ '**/?(*.)(spec).js?(x)' - // '**/?(*.)(spec|test).js?(x)' ], transform: { - "^.+\\.[t|j]sx?$": "babel-jest" - }, + '^.+\\.[t|j]sx?$': 'babel-jest' + } }; diff --git a/package.json b/package.json index dd14538..431113b 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "sequelize": "^5.21.13", "sharp": "^0.29.0", "svg-captcha": "^1.4.0", - "validator": "^13.1.1", + "validator": "^13.7.0", "ws": "^7.4.4" } } From 0e6958907aac6d7327a2fc6ac3ca95408b1bcfa9 Mon Sep 17 00:00:00 2001 From: Evan Wang <525650856@qq.com> Date: Mon, 8 Nov 2021 09:35:09 +0800 Subject: [PATCH 5/5] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e611230..9f9ad24 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ QQ 群号:643205479 / 814597236 ### 0.3.12 1. `A` 新增验证码功能,默认关闭验证码 +2. `U` assets 目录用作本地文件上传,移到项目根目录 + ### 0.3.11 1. `F` 修复消息中心 API 调用拼写错误

AltStyle によって変換されたページ (->オリジナル) /