新增规则触发人机验证和加载失败提示
This commit is contained in:
@@ -1 +1,2 @@
|
||||
import app from '../hono/hono';
|
||||
import result from '../model/result';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import verifyRecordService from '../service/verify-record-service';
|
||||
|
||||
export const userConst = {
|
||||
status: {
|
||||
NORMAL: 0,
|
||||
@@ -74,7 +76,7 @@ export const settingConst = {
|
||||
},
|
||||
addEmail: {
|
||||
OPEN: 0,
|
||||
CLOSE: 1,
|
||||
CLOSE: 1
|
||||
},
|
||||
manyEmail: {
|
||||
OPEN: 0,
|
||||
@@ -83,10 +85,12 @@ export const settingConst = {
|
||||
registerVerify: {
|
||||
OPEN: 0,
|
||||
CLOSE: 1,
|
||||
COUNT: 2,
|
||||
},
|
||||
addEmailVerify: {
|
||||
OPEN: 0,
|
||||
CLOSE: 1,
|
||||
COUNT: 2,
|
||||
},
|
||||
forwardStatus: {
|
||||
OPEN: 0,
|
||||
@@ -102,6 +106,11 @@ export const settingConst = {
|
||||
}
|
||||
}
|
||||
|
||||
export const verifyRecordType = {
|
||||
REG: 0,
|
||||
ADD: 1,
|
||||
}
|
||||
|
||||
|
||||
export const isDel = {
|
||||
DELETE: 1,
|
||||
|
||||
@@ -8,6 +8,8 @@ export const setting = sqliteTable('setting', {
|
||||
autoRefreshTime: integer('auto_refresh_time').default(0).notNull(),
|
||||
addEmailVerify: integer('add_email_verify').default(1).notNull(),
|
||||
registerVerify: integer('register_verify').default(1).notNull(),
|
||||
regVerifyCount: integer('reg_verify_count').default(1).notNull(),
|
||||
addVerifyCount: integer('add_verify_count').default(1).notNull(),
|
||||
send: integer('send').default(1).notNull(),
|
||||
r2Domain: text('r2_domain'),
|
||||
secretKey: text('secret_key'),
|
||||
|
||||
10
mail-worker/src/entity/verify-record.js
Normal file
10
mail-worker/src/entity/verify-record.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { sqliteTable, text, integer} from 'drizzle-orm/sqlite-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
export const email = sqliteTable('verify_record', {
|
||||
vrId: integer('vr_id').primaryKey({ autoIncrement: true }),
|
||||
ip: integer('ip').notNull().default(''),
|
||||
count: integer('count').notNull().default(1),
|
||||
type: integer('type').notNull().default(0),
|
||||
updateTime: text('update_time').default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||
});
|
||||
export default email
|
||||
@@ -48,7 +48,7 @@ const en = {
|
||||
noOsUpBack: 'Cannot upload background: R2 object storage not configured',
|
||||
noOsDomainUpBack: 'Cannot upload background: R2 domain not configured',
|
||||
starNotExistEmail: 'Starred email does not exist',
|
||||
emptyBotToken: 'Verification token cannot be empty',
|
||||
emptyBotToken: 'Please verify that you are human',
|
||||
botVerifyFail: 'Bot verification failed, please try again',
|
||||
authExpired: 'Authentication has expired. Please sign in again',
|
||||
unauthorized: 'Unauthorized',
|
||||
|
||||
@@ -48,7 +48,7 @@ const zh = {
|
||||
noOsUpBack: 'r2对象存储未配置不能上传背景',
|
||||
noOsDomainUpBack: 'r2域名未配置不能上传背景',
|
||||
starNotExistEmail: '星标的邮件不存在',
|
||||
emptyBotToken: '验证token不能为空',
|
||||
emptyBotToken: '需要进行人机验证',
|
||||
botVerifyFail: '人机验证失败,请重试',
|
||||
authExpired: '身份认证失效,请重新登录',
|
||||
unauthorized: '权限不足',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import app from './hono/webs';
|
||||
import { email } from './email/email';
|
||||
import userService from './service/user-service';
|
||||
import verifyRecord from './entity/verify-record';
|
||||
import verifyRecordService from './service/verify-record-service';
|
||||
export default {
|
||||
async fetch(req, env, ctx) {
|
||||
const url = new URL(req.url)
|
||||
@@ -16,6 +18,7 @@ export default {
|
||||
},
|
||||
email: email,
|
||||
async scheduled(c, env, ctx) {
|
||||
await verifyRecordService.clearRecord({env})
|
||||
await userService.resetDaySendCount({ env })
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,10 +18,34 @@ const init = {
|
||||
await this.v1_3_1DB(c);
|
||||
await this.v1_4DB(c);
|
||||
await this.v1_5DB(c);
|
||||
await this.v1_6DB(c);
|
||||
await settingService.refresh(c);
|
||||
return c.text(t('initSuccess'));
|
||||
},
|
||||
|
||||
async v1_6DB(c) {
|
||||
|
||||
const ADD_COLUMN_SQL_LIST = [
|
||||
`ALTER TABLE setting ADD COLUMN reg_verify_count INTEGER NOT NULL DEFAULT 1;`,
|
||||
`ALTER TABLE setting ADD COLUMN add_verify_count INTEGER NOT NULL DEFAULT 1;`,
|
||||
`CREATE TABLE IF NOT EXISTS verify_record (
|
||||
vr_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip TEXT NOT NULL DEFAULT '',
|
||||
count INTEGER NOT NULL DEFAULT 1,
|
||||
type INTEGER NOT NULL DEFAULT 0,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`
|
||||
];
|
||||
|
||||
for (let sql of ADD_COLUMN_SQL_LIST) {
|
||||
try {
|
||||
await c.env.db.prepare(sql).run();
|
||||
} catch (e) {
|
||||
console.warn(`跳过字段添加,原因:${e.message}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async v1_5DB(c) {
|
||||
await c.env.db.prepare(`UPDATE perm SET perm_key = 'sys-email:list' WHERE perm_key = 'all-email:list'`).run();
|
||||
await c.env.db.prepare(`UPDATE perm SET perm_key = 'sys-email:delete' WHERE perm_key = 'all-email:delete'`).run();
|
||||
|
||||
@@ -6,21 +6,26 @@ import emailService from './email-service';
|
||||
import orm from '../entity/orm';
|
||||
import account from '../entity/account';
|
||||
import { and, asc, eq, gt, inArray, count, sql } from 'drizzle-orm';
|
||||
import { isDel } from '../const/entity-const';
|
||||
import { isDel, settingConst } from '../const/entity-const';
|
||||
import settingService from './setting-service';
|
||||
import turnstileService from './turnstile-service';
|
||||
import roleService from './role-service';
|
||||
import { t } from '../i18n/i18n';
|
||||
import verifyRecordService from './verify-record-service';
|
||||
|
||||
const accountService = {
|
||||
|
||||
async add(c, params, userId) {
|
||||
|
||||
if (!await settingService.isAddEmail(c)) {
|
||||
const {addEmailVerify , addEmail, manyEmail, addVerifyCount} = await settingService.query(c);
|
||||
|
||||
let { email, token } = params;
|
||||
|
||||
|
||||
if (!(addEmail === settingConst.addEmail.OPEN && manyEmail === settingConst.manyEmail.OPEN)) {
|
||||
throw new BizError(t('addAccountDisabled'));
|
||||
}
|
||||
|
||||
let { email, token } = params;
|
||||
|
||||
if (!email) {
|
||||
throw new BizError(t('emptyEmail'));
|
||||
@@ -35,7 +40,7 @@ const accountService = {
|
||||
}
|
||||
|
||||
|
||||
const accountRow = await this.selectByEmailIncludeDelNoCase(c, email);
|
||||
let accountRow = await this.selectByEmailIncludeDelNoCase(c, email);
|
||||
|
||||
if (accountRow && accountRow.isDel === isDel.DELETE) {
|
||||
throw new BizError(t('isDelAccount'));
|
||||
@@ -61,11 +66,30 @@ const accountService = {
|
||||
|
||||
}
|
||||
|
||||
if (await settingService.isAddEmailVerify(c)) {
|
||||
let addVerifyOpen = false
|
||||
|
||||
if (addEmailVerify === settingConst.addEmailVerify.OPEN) {
|
||||
addVerifyOpen = true
|
||||
await turnstileService.verify(c, token);
|
||||
}
|
||||
|
||||
return orm(c).insert(account).values({ email: email, userId: userId, name: emailUtils.getName(email) }).returning().get();
|
||||
if (addEmailVerify === settingConst.addEmailVerify.COUNT) {
|
||||
addVerifyOpen = await verifyRecordService.isOpenAddVerify(c, addVerifyCount);
|
||||
if (addVerifyOpen) {
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
accountRow = await orm(c).insert(account).values({ email: email, userId: userId, name: emailUtils.getName(email) }).returning().get();
|
||||
|
||||
if (addEmailVerify === settingConst.addEmailVerify.COUNT && !addVerifyOpen) {
|
||||
const row = await verifyRecordService.increaseAddCount(c);
|
||||
addVerifyOpen = row.count >= addVerifyCount
|
||||
}
|
||||
|
||||
accountRow.addVerifyOpen = addVerifyOpen
|
||||
return accountRow;
|
||||
},
|
||||
|
||||
selectByEmailIncludeDelNoCase(c, email) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import regKeyService from './reg-key-service';
|
||||
import dayjs from 'dayjs';
|
||||
import { toUtc } from '../utils/date-uitil';
|
||||
import { t } from '../i18n/i18n.js';
|
||||
import verifyRecordService from './verify-record-service';
|
||||
|
||||
const loginService = {
|
||||
|
||||
@@ -25,11 +26,8 @@ const loginService = {
|
||||
|
||||
const { email, password, token, code } = params;
|
||||
|
||||
const {regKey, register, registerVerify} = await settingService.query(c)
|
||||
const {regKey, register, registerVerify, regVerifyCount} = await settingService.query(c)
|
||||
|
||||
if (registerVerify === settingConst.registerVerify.OPEN) {
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
|
||||
if (register === settingConst.register.CLOSE) {
|
||||
throw new BizError(t('regDisabled'));
|
||||
@@ -80,7 +78,6 @@ const loginService = {
|
||||
throw new BizError(t('isRegAccount'));
|
||||
}
|
||||
|
||||
const { salt, hash } = await saltHashUtils.hashPassword(password);
|
||||
|
||||
let defType = null
|
||||
|
||||
@@ -104,6 +101,22 @@ const loginService = {
|
||||
|
||||
}
|
||||
|
||||
let regVerifyOpen = false
|
||||
|
||||
if (registerVerify === settingConst.registerVerify.OPEN) {
|
||||
regVerifyOpen = true
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
|
||||
if (registerVerify === settingConst.registerVerify.COUNT) {
|
||||
regVerifyOpen = await verifyRecordService.isOpenRegVerify(c, regVerifyCount);
|
||||
if (regVerifyOpen) {
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
}
|
||||
|
||||
const { salt, hash } = await saltHashUtils.hashPassword(password);
|
||||
|
||||
const userId = await userService.insert(c, { email, regKeyId,password: hash, salt, type: type || defType });
|
||||
|
||||
await userService.updateUserInfo(c, userId, true);
|
||||
@@ -114,6 +127,13 @@ const loginService = {
|
||||
await regKeyService.reduceCount(c, code, 1);
|
||||
}
|
||||
|
||||
if (registerVerify === settingConst.registerVerify.COUNT && !regVerifyOpen) {
|
||||
const row = await verifyRecordService.increaseRegCount(c);
|
||||
return {regVerifyOpen: row.count >= regVerifyCount}
|
||||
}
|
||||
|
||||
return {regVerifyOpen}
|
||||
|
||||
},
|
||||
|
||||
async handleOpenRegKey(c, regKey, code) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import KvConst from '../const/kv-const';
|
||||
import setting from '../entity/setting';
|
||||
import orm from '../entity/orm';
|
||||
import { settingConst } from '../const/entity-const';
|
||||
import { settingConst, verifyRecordType } from '../const/entity-const';
|
||||
import fileUtils from '../utils/file-utils';
|
||||
import r2Service from './r2-service';
|
||||
import emailService from './email-service';
|
||||
@@ -10,6 +10,7 @@ import userService from './user-service';
|
||||
import constant from '../const/constant';
|
||||
import BizError from '../error/biz-error';
|
||||
import { t } from '../i18n/i18n'
|
||||
import verifyRecordService from './verify-record-service';
|
||||
|
||||
const settingService = {
|
||||
|
||||
@@ -31,11 +32,32 @@ const settingService = {
|
||||
},
|
||||
|
||||
async get(c) {
|
||||
const settingRow = await this.query(c);
|
||||
|
||||
const [settingRow, recordList] = await Promise.all([
|
||||
await this.query(c),
|
||||
verifyRecordService.selectListByIP(c)
|
||||
]);
|
||||
|
||||
settingRow.secretKey = settingRow.secretKey ? `${settingRow.secretKey.slice(0, 12)}******` : null;
|
||||
Object.keys(settingRow.resendTokens).forEach(key => {
|
||||
settingRow.resendTokens[key] = `${settingRow.resendTokens[key].slice(0, 12)}******`;
|
||||
});
|
||||
|
||||
let regVerifyOpen = false
|
||||
let addVerifyOpen = false
|
||||
|
||||
recordList.forEach(row => {
|
||||
if (row.type === verifyRecordType.REG) {
|
||||
regVerifyOpen = row.count >= settingRow.regVerifyCount
|
||||
}
|
||||
if (row.type === verifyRecordType.ADD) {
|
||||
addVerifyOpen = row.count >= settingRow.addVerifyCount
|
||||
}
|
||||
})
|
||||
|
||||
settingRow.regVerifyOpen = regVerifyOpen
|
||||
settingRow.addVerifyOpen = addVerifyOpen
|
||||
|
||||
return settingRow;
|
||||
},
|
||||
|
||||
@@ -50,28 +72,13 @@ const settingService = {
|
||||
await this.refresh(c);
|
||||
},
|
||||
|
||||
async isAddEmail(c) {
|
||||
const { addEmail, manyEmail } = await this.query(c);
|
||||
return addEmail === settingConst.addEmail.OPEN && manyEmail === settingConst.manyEmail.OPEN;
|
||||
},
|
||||
|
||||
async isRegisterVerify(c) {
|
||||
const { registerVerify } = await this.query(c);
|
||||
return registerVerify === settingConst.registerVerify.OPEN;
|
||||
},
|
||||
|
||||
async isAddEmailVerify(c) {
|
||||
const { addEmailVerify } = await this.query(c);
|
||||
return addEmailVerify === settingConst.addEmailVerify.OPEN;
|
||||
},
|
||||
|
||||
async setBackground(c, params) {
|
||||
|
||||
const settingRow = await this.query(c);
|
||||
|
||||
let { background } = params
|
||||
|
||||
if (!background.startsWith('http')) {
|
||||
if (background && !background.startsWith('http')) {
|
||||
|
||||
if (!c.env.r2) {
|
||||
throw new BizError(t('noOsUpBack'));
|
||||
@@ -113,7 +120,9 @@ const settingService = {
|
||||
},
|
||||
|
||||
async websiteConfig(c) {
|
||||
const settingRow = await this.get(c);
|
||||
|
||||
const settingRow = await this.get(c)
|
||||
|
||||
return {
|
||||
register: settingRow.register,
|
||||
title: settingRow.title,
|
||||
@@ -128,7 +137,9 @@ const settingService = {
|
||||
background: settingRow.background,
|
||||
loginOpacity: settingRow.loginOpacity,
|
||||
domainList:settingRow.domainList,
|
||||
regKey: settingRow.regKey
|
||||
regKey: settingRow.regKey,
|
||||
regVerifyOpen: settingRow.regVerifyOpen,
|
||||
addVerifyOpen: settingRow.addVerifyOpen
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ const turnstileService = {
|
||||
async verify(c, token) {
|
||||
|
||||
if (!token) {
|
||||
throw new BizError(t('emptyBotToken'));
|
||||
throw new BizError(t('emptyBotToken'),400);
|
||||
}
|
||||
|
||||
const settingRow = await settingService.query(c)
|
||||
|
||||
90
mail-worker/src/service/verify-record-service.js
Normal file
90
mail-worker/src/service/verify-record-service.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import orm from '../entity/orm';
|
||||
import verifyRecord from '../entity/verify-record';
|
||||
import { eq, sql, and } from 'drizzle-orm';
|
||||
import dayjs from 'dayjs';
|
||||
import ipUtils from '../utils/ip-utils';
|
||||
import { verifyRecordType } from '../const/entity-const';
|
||||
|
||||
const verifyRecordService = {
|
||||
|
||||
async selectListByIP(c) {
|
||||
const ip = ipUtils.getIp(c)
|
||||
return orm(c).select().from(verifyRecord).where(eq(verifyRecord.ip, ip)).all();
|
||||
},
|
||||
|
||||
async clearRecord(c) {
|
||||
await orm(c).delete(verifyRecord).run();
|
||||
},
|
||||
|
||||
async isOpenRegVerify(c, regVerifyCount) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.REG))).get();
|
||||
|
||||
if (row) {
|
||||
if (row.count >= regVerifyCount){
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
},
|
||||
|
||||
async isOpenAddVerify(c, addVerifyCount) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.ADD))).get();
|
||||
|
||||
if (row) {
|
||||
|
||||
if (row.count >= addVerifyCount){
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
},
|
||||
|
||||
async increaseRegCount(c) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.REG))).get();
|
||||
const now = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
if (row) {
|
||||
return orm(c).update(verifyRecord).set({
|
||||
count: sql`${verifyRecord.count}
|
||||
+ 1`, updateTime: now
|
||||
}).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.REG))).returning().get();
|
||||
} else {
|
||||
return orm(c).insert(verifyRecord).values({ip, type: verifyRecordType.REG}).returning().run();
|
||||
}
|
||||
},
|
||||
|
||||
async increaseAddCount(c) {
|
||||
|
||||
const ip = ipUtils.getIp(c)
|
||||
|
||||
const row = await orm(c).select().from(verifyRecord).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.ADD))).get();
|
||||
const now = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
if (row) {
|
||||
return orm(c).update(verifyRecord).set({
|
||||
count: sql`${verifyRecord.count}
|
||||
+ 1`, updateTime: now
|
||||
}).where(and(eq(verifyRecord.ip, ip),eq(verifyRecord.type,verifyRecordType.ADD))).returning().get();
|
||||
} else {
|
||||
return orm(c).insert(verifyRecord).values({ip, type: verifyRecordType.ADD}).returning().get();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default verifyRecordService;
|
||||
9
mail-worker/src/utils/ip-utils.js
Normal file
9
mail-worker/src/utils/ip-utils.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const ipUtils = {
|
||||
getIp(c) {
|
||||
return c.req.header('CF-Connecting-IP') ||
|
||||
c.req.header('X-Forwarded-For') ||
|
||||
'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export default ipUtils
|
||||
Reference in New Issue
Block a user