新增权限域名限制和链接背景上传
This commit is contained in:
@@ -8,11 +8,12 @@ import fileUtils from '../utils/file-utils';
|
||||
import { emailConst, isDel, roleConst, settingConst } from '../const/entity-const';
|
||||
import emailUtils from '../utils/email-utils';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import roleService from '../service/role-service';
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export async function email(message, env, ctx) {
|
||||
|
||||
@@ -50,25 +51,29 @@ export async function email(message, env, ctx) {
|
||||
|
||||
if (account && account.email !== env.admin) {
|
||||
|
||||
let { banEmail, banEmailType } = await roleService.selectByUserId({ env: env}, account.userId);
|
||||
let { banEmail, banEmailType, availDomain } = await roleService.selectByUserId({ env: env }, account.userId);
|
||||
|
||||
banEmail = banEmail.split(",").filter(item => item !== "")
|
||||
if(!roleService.hasAvailDomainPerm(availDomain, message.to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
banEmail = banEmail.split(',').filter(item => item !== '');
|
||||
|
||||
for (const item of banEmail) {
|
||||
|
||||
if (item.startsWith('*@')) {
|
||||
|
||||
const banDomain = emailUtils.getDomain(item.toLowerCase())
|
||||
const receiveDomain = emailUtils.getDomain(email.from.address.toLowerCase())
|
||||
const banDomain = emailUtils.getDomain(item.toLowerCase());
|
||||
const receiveDomain = emailUtils.getDomain(email.from.address.toLowerCase());
|
||||
|
||||
if (banDomain === receiveDomain) {
|
||||
|
||||
if (banEmailType === roleConst.banEmailType.ALL) return
|
||||
if (banEmailType === roleConst.banEmailType.ALL) return;
|
||||
|
||||
if (banEmailType === roleConst.banEmailType.CONTENT) {
|
||||
email.html = 'messageRemoved'
|
||||
email.text = 'messageRemoved'
|
||||
email.attachments = []
|
||||
email.html = 'The content has been deleted';
|
||||
email.text = 'The content has been deleted';
|
||||
email.attachments = [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -77,12 +82,12 @@ export async function email(message, env, ctx) {
|
||||
|
||||
if (item.toLowerCase() === email.from.address.toLowerCase()) {
|
||||
|
||||
if (banEmailType === roleConst.banEmailType.ALL) return
|
||||
if (banEmailType === roleConst.banEmailType.ALL) return;
|
||||
|
||||
if (banEmailType === roleConst.banEmailType.CONTENT) {
|
||||
email.html = 'messageRemoved'
|
||||
email.text = 'messageRemoved'
|
||||
email.attachments = []
|
||||
email.html = 'The content has been deleted';
|
||||
email.text = 'The content has been deleted';
|
||||
email.attachments = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export const role = sqliteTable('role', {
|
||||
description: text('description'),
|
||||
banEmail: text('ban_email').notNull().default(''),
|
||||
banEmailType: integer('ban_email_type').notNull().default(0),
|
||||
availDomain: text('avail_domain').default(''),
|
||||
sort: integer('sort'),
|
||||
isDefault: integer('is_default').default(0),
|
||||
createTime: text('create_time').default(sql`CURRENT_TIMESTAMP`).notNull(),
|
||||
|
||||
@@ -54,6 +54,11 @@ const en = {
|
||||
unauthorized: 'Unauthorized',
|
||||
bannedSend: 'You are banned from sending emails',
|
||||
initSuccess: 'Successfully initialized',
|
||||
noDomainPermAdd: "No permission to add this domain email",
|
||||
noDomainPermReg: "No permission to register this domain email",
|
||||
noDomainPermRegKey: "Registration code not valid for this domain",
|
||||
noDomainPermSend: "No permission to send from this domain email",
|
||||
JWTMismatch: 'JWT secret mismatch',
|
||||
perms: {
|
||||
"邮件": "Email",
|
||||
"邮件发送": "Send email",
|
||||
|
||||
@@ -46,14 +46,19 @@ const zh = {
|
||||
delDefRole: '默认身份不能删除',
|
||||
notJsonDomain: '环境变量domain必须是JSON类型',
|
||||
noOsUpBack: 'r2对象存储未配置不能上传背景',
|
||||
noOsDomainUpBack: 'r2域名未配置不上传背景',
|
||||
noOsDomainUpBack: 'r2域名未配置不能上传背景',
|
||||
starNotExistEmail: '星标的邮件不存在',
|
||||
emptyBotToken: '验证token不能为空',
|
||||
botVerifyFail: '人机验证失败,请重试',
|
||||
authExpired: '身份认证失效,请重新登录',
|
||||
unauthorized: '权限不足',
|
||||
bannedSend: '你已被禁止发送邮件',
|
||||
initSuccess: 'Successfully initialized',
|
||||
initSuccess: '初始化成功',
|
||||
noDomainPermAdd: '你没有权限添加该域名邮箱',
|
||||
noDomainPermReg: '你没有权限注册该域名邮箱',
|
||||
noDomainPermRegKey: '你的注册码没有权限注册该域名邮箱',
|
||||
noDomainPermSend: '你没有权限使用该域名邮箱发送邮件',
|
||||
JWTMismatch: 'jwt_secret 不匹配',
|
||||
perms: {
|
||||
"邮件": "邮件",
|
||||
"邮件发送": "邮件发送",
|
||||
|
||||
@@ -8,7 +8,7 @@ const init = {
|
||||
const secret = c.req.param('secret');
|
||||
|
||||
if (secret !== c.env.jwt_secret) {
|
||||
return c.text(t('initSuccess'));
|
||||
return c.text('jwt_secret 不匹配');
|
||||
}
|
||||
|
||||
await this.intDB(c);
|
||||
@@ -19,12 +19,17 @@ const init = {
|
||||
await this.v1_4DB(c);
|
||||
await this.v1_5DB(c);
|
||||
await settingService.refresh(c);
|
||||
return c.text('初始化成功');
|
||||
return c.text(t('initSuccess'));
|
||||
},
|
||||
|
||||
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();
|
||||
try {
|
||||
await c.env.db.prepare(`ALTER TABLE role ADD COLUMN avail_domain TEXT NOT NULL DEFAULT ''`).run();
|
||||
} catch (e) {
|
||||
console.warn(`跳过字段添加,原因:${e.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
async v1_4DB(c) {
|
||||
|
||||
@@ -126,7 +126,6 @@ app.use('*', async (c, next) => {
|
||||
const userPaths = permKeyToPaths(permKeys);
|
||||
|
||||
const userPermIndex = userPaths.findIndex(item => {
|
||||
console.log(item)
|
||||
return path.startsWith(item);
|
||||
});
|
||||
|
||||
|
||||
@@ -48,9 +48,17 @@ const accountService = {
|
||||
const userRow = await userService.selectById(c, userId);
|
||||
const roleRow = await roleService.selectById(c, userRow.type);
|
||||
|
||||
if (roleRow.accountCount && userRow.email !== c.env.admin) {
|
||||
const userAccountCount = await accountService.countUserAccount(c, userId)
|
||||
if(userAccountCount >= roleRow.accountCount) throw new BizError(t('accountLimit'), 403);
|
||||
if (userRow.email !== c.env.admin) {
|
||||
|
||||
if (roleRow.accountCount > 0) {
|
||||
const userAccountCount = await accountService.countUserAccount(c, userId)
|
||||
if(userAccountCount >= roleRow.accountCount) throw new BizError(t('accountLimit'), 403);
|
||||
}
|
||||
|
||||
if(!roleService.hasAvailDomainPerm(roleRow.availDomain, email)) {
|
||||
throw new BizError(t('noDomainPermAdd'),403)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (await settingService.isAddEmailVerify(c)) {
|
||||
|
||||
@@ -159,6 +159,7 @@ const emailService = {
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (attDataList.length > 0 && !r2Domain) {
|
||||
throw new BizError(t('noOsDomainSendPic'));
|
||||
}
|
||||
@@ -186,6 +187,18 @@ const emailService = {
|
||||
throw new BizError(t('senderAccountNotExist'));
|
||||
}
|
||||
|
||||
if (accountRow.userId !== userId) {
|
||||
throw new BizError(t('sendEmailNotCurUser'));
|
||||
}
|
||||
|
||||
if (c.env.admin !== userRow.email) {
|
||||
|
||||
if(!roleService.hasAvailDomainPerm(roleRow.availDomain, accountRow.email)) {
|
||||
throw new BizError(t('noDomainPermSend'),403)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const domain = emailUtils.getDomain(accountRow.email);
|
||||
const resendToken = resendTokens[domain];
|
||||
|
||||
@@ -194,10 +207,6 @@ const emailService = {
|
||||
}
|
||||
|
||||
|
||||
if (accountRow.userId !== userId) {
|
||||
throw new BizError(t('sendEmailNotCurUser'));
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = emailUtils.getName(accountRow.email);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,11 @@ const loginService = {
|
||||
|
||||
const { email, password, token, code } = params;
|
||||
|
||||
const {regKey, register} = await settingService.query(c)
|
||||
const {regKey, register, registerVerify} = await settingService.query(c)
|
||||
|
||||
if (registerVerify === settingConst.registerVerify.OPEN) {
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
|
||||
if (register === settingConst.register.CLOSE) {
|
||||
throw new BizError(t('regDisabled'));
|
||||
@@ -76,10 +80,6 @@ const loginService = {
|
||||
throw new BizError(t('isRegAccount'));
|
||||
}
|
||||
|
||||
if (await settingService.isRegisterVerify(c)) {
|
||||
await turnstileService.verify(c,token)
|
||||
}
|
||||
|
||||
const { salt, hash } = await saltHashUtils.hashPassword(password);
|
||||
|
||||
let defType = null
|
||||
@@ -89,6 +89,21 @@ const loginService = {
|
||||
defType = roleRow.roleId
|
||||
}
|
||||
|
||||
|
||||
const roleRow = await roleService.selectById(c, type || defType);
|
||||
|
||||
if(!roleService.hasAvailDomainPerm(roleRow.availDomain, email)) {
|
||||
|
||||
if (type) {
|
||||
throw new BizError(t('noDomainPermRegKey'),403)
|
||||
}
|
||||
|
||||
if (defType) {
|
||||
throw new BizError(t('noDomainPermReg'),403)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const userId = await userService.insert(c, { email, regKeyId,password: hash, salt, type: type || defType });
|
||||
|
||||
await userService.updateUserInfo(c, userId, true);
|
||||
|
||||
@@ -9,12 +9,13 @@ import userService from './user-service';
|
||||
import user from '../entity/user';
|
||||
import verifyUtils from '../utils/verify-utils';
|
||||
import { t } from '../i18n/i18n.js';
|
||||
import emailUtils from '../utils/email-utils';
|
||||
|
||||
const roleService = {
|
||||
|
||||
async add(c, params, userId) {
|
||||
|
||||
let { name, permIds, banEmail } = params;
|
||||
let { name, permIds, banEmail, availDomain } = params;
|
||||
|
||||
if (!name) {
|
||||
throw new BizError(t('emptyRoleName'));
|
||||
@@ -32,9 +33,11 @@ const roleService = {
|
||||
throw new BizError(t('notEmail'));
|
||||
}
|
||||
|
||||
banEmail = banEmail.join(',')
|
||||
banEmail = banEmail.join(',');
|
||||
|
||||
roleRow = await orm(c).insert(role).values({...params, banEmail, userId}).returning().get();
|
||||
availDomain = availDomain.join(',');
|
||||
|
||||
roleRow = await orm(c).insert(role).values({...params, banEmail, availDomain, userId}).returning().get();
|
||||
|
||||
if (permIds.length === 0) {
|
||||
return;
|
||||
@@ -55,7 +58,8 @@ const roleService = {
|
||||
.where(eq(perm.type, permConst.type.BUTTON)).all();
|
||||
|
||||
roleList.forEach(role => {
|
||||
role.banEmail = role.banEmail.split(",").filter(item => item !== "")
|
||||
role.banEmail = role.banEmail.split(",").filter(item => item !== "");
|
||||
role.availDomain = role.availDomain.split(",").filter(item => item !== "");
|
||||
role.permIds = permList.filter(perm => perm.roleId === role.roleId).map(perm => perm.permId);
|
||||
});
|
||||
|
||||
@@ -64,7 +68,7 @@ const roleService = {
|
||||
|
||||
async setRole(c, params) {
|
||||
|
||||
let { name, permIds, roleId, banEmail } = params;
|
||||
let { name, permIds, roleId, banEmail, availDomain } = params;
|
||||
|
||||
if (!name) {
|
||||
throw new BizError(t('emptyRoleName'));
|
||||
@@ -80,7 +84,9 @@ const roleService = {
|
||||
|
||||
banEmail = banEmail.join(',')
|
||||
|
||||
await orm(c).update(role).set({...params, banEmail}).where(eq(role.roleId, roleId)).run();
|
||||
availDomain = availDomain.join(',')
|
||||
|
||||
await orm(c).update(role).set({...params, banEmail, availDomain}).where(eq(role.roleId, roleId)).run();
|
||||
await orm(c).delete(rolePerm).where(eq(rolePerm.roleId, roleId)).run();
|
||||
|
||||
if (permIds.length > 0) {
|
||||
@@ -150,6 +156,24 @@ const roleService = {
|
||||
|
||||
selectByUserId(c, userId) {
|
||||
return orm(c).select(role).from(user).leftJoin(role, eq(role.roleId, user.type)).where(eq(user.userId, userId)).get();
|
||||
},
|
||||
|
||||
hasAvailDomainPerm(availDomain, email) {
|
||||
|
||||
availDomain = availDomain.split(',').filter(item => item !== '');
|
||||
|
||||
if (availDomain.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
const availIndex = availDomain.findIndex(item => {
|
||||
const domain = emailUtils.getDomain(email.toLowerCase());
|
||||
const availDomainItem = emailUtils.getDomain(item.toLowerCase());
|
||||
console.log(domain,availDomainItem)
|
||||
return domain === availDomainItem
|
||||
})
|
||||
|
||||
return availIndex > -1
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -69,30 +69,41 @@ const settingService = {
|
||||
|
||||
const settingRow = await this.query(c);
|
||||
|
||||
let { background } = params
|
||||
|
||||
if (!background.startsWith('http')) {
|
||||
|
||||
if (!c.env.r2) {
|
||||
throw new BizError(t('noOsUpBack'));
|
||||
}
|
||||
|
||||
if (!settingRow.r2Domain) {
|
||||
throw new BizError(t('noOsDomainUpBack'));
|
||||
}
|
||||
|
||||
const file = fileUtils.base64ToFile(background)
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
background = constant.BACKGROUND_PREFIX + await fileUtils.getBuffHash(arrayBuffer) + fileUtils.getExtFileName(file.name);
|
||||
|
||||
|
||||
await r2Service.putObj(c, background, arrayBuffer, {
|
||||
contentType: file.type
|
||||
});
|
||||
|
||||
if (!c.env.r2) {
|
||||
throw new BizError(t('noOsUpBack'));
|
||||
}
|
||||
|
||||
if (!settingRow.r2Domain) {
|
||||
throw new BizError(t('noOsDomainUpBack'));
|
||||
}
|
||||
|
||||
const { background } = params;
|
||||
const file = fileUtils.base64ToFile(background);
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const key = constant.BACKGROUND_PREFIX + await fileUtils.getBuffHash(arrayBuffer) + fileUtils.getExtFileName(file.name);
|
||||
await r2Service.putObj(c, key, file, {
|
||||
contentType: file.type
|
||||
});
|
||||
|
||||
if (settingRow.background) {
|
||||
await r2Service.delete(c, settingRow.background);
|
||||
try {
|
||||
await r2Service.delete(c, settingRow.background);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
await orm(c).update(setting).set({ background: key }).run();
|
||||
await orm(c).update(setting).set({ background }).run();
|
||||
await this.refresh(c);
|
||||
return key;
|
||||
return background;
|
||||
},
|
||||
|
||||
async physicsDeleteAll(c) {
|
||||
|
||||
@@ -27,7 +27,7 @@ const fileUtils = {
|
||||
* @returns {File} File 对象
|
||||
*/
|
||||
base64ToFile(base64Data, customFilename) {
|
||||
const match = base64Data.match(/^data:(image|video)\/([a-zA-Z0-9.+-]+);base64,/);
|
||||
const match = base64Data.match(/^data:(image|jpeg|video)\/([a-zA-Z0-9.+-]+);base64,/);
|
||||
if (!match) {
|
||||
throw new Error('Invalid base64 data format');
|
||||
}
|
||||
@@ -35,7 +35,7 @@ const fileUtils = {
|
||||
const type = match[1]; // image 或 video
|
||||
const ext = match[2]; // jpg, png, mp4 等
|
||||
const mimeType = `${type}/${ext}`;
|
||||
const cleanBase64 = base64Data.replace(/^data:(image|video)\/[a-zA-Z0-9.+-]+;base64,/, '');
|
||||
const cleanBase64 = base64Data.replace(/^data:(image|jpeg|video)\/[a-zA-Z0-9.+-]+;base64,/, '');
|
||||
|
||||
const byteCharacters = atob(cleanBase64);
|
||||
const byteArrays = [];
|
||||
|
||||
Reference in New Issue
Block a user