mirror of
https://github.com/schroinerxy/cloud-mail.git
synced 2026-06-21 19:35:50 +08:00
新增注册邮箱名自定义字符过滤
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
const en = {
|
const en = {
|
||||||
'收件箱': 'Inbox',
|
inbox: 'Inbox',
|
||||||
drafts: 'Drafts',
|
drafts: 'Drafts',
|
||||||
sent: 'Sent',
|
sent: 'Sent',
|
||||||
starred: 'Starred',
|
starred: 'Starred',
|
||||||
@@ -307,7 +307,9 @@ const en = {
|
|||||||
emailText: 'Email Text',
|
emailText: 'Email Text',
|
||||||
emailPrefix: 'Email Prefix',
|
emailPrefix: 'Email Prefix',
|
||||||
atLeast: 'At Least',
|
atLeast: 'At Least',
|
||||||
character: ''
|
character: '',
|
||||||
|
mustNotContain: 'Must Not Contain',
|
||||||
|
mustNotContainDesc: 'Separate with commas'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default en
|
export default en
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const zh = {
|
const zh = {
|
||||||
'收件箱': '收件箱',
|
inbox: '收件箱',
|
||||||
drafts: '草稿箱',
|
drafts: '草稿箱',
|
||||||
sent: '已发送',
|
sent: '已发送',
|
||||||
starred: '星标邮件',
|
starred: '星标邮件',
|
||||||
@@ -192,8 +192,8 @@ const zh = {
|
|||||||
emptyEmailMsg: '邮箱不能为空',
|
emptyEmailMsg: '邮箱不能为空',
|
||||||
notEmailMsg: '输入的邮箱不合法',
|
notEmailMsg: '输入的邮箱不合法',
|
||||||
emptyPwdMsg: '密码不能为空',
|
emptyPwdMsg: '密码不能为空',
|
||||||
pwdLengthMsg: '密码最少六位',
|
pwdLengthMsg: '密码至少六位',
|
||||||
minEmailPrefix: '邮箱名不能小于{msg}位',
|
minEmailPrefix: '邮箱名至少{msg}位',
|
||||||
confirmPwdFailMsg: '两次密码输入不一致',
|
confirmPwdFailMsg: '两次密码输入不一致',
|
||||||
emptyRegKeyMsg: '注册码不能为空',
|
emptyRegKeyMsg: '注册码不能为空',
|
||||||
regSuccessMsg: '注册成功',
|
regSuccessMsg: '注册成功',
|
||||||
@@ -228,7 +228,7 @@ const zh = {
|
|||||||
banRestore: '确认禁用 {msg} 吗?',
|
banRestore: '确认禁用 {msg} 吗?',
|
||||||
logOut: '退出',
|
logOut: '退出',
|
||||||
clearContentConfirm: '确定要清空所有内容吗?',
|
clearContentConfirm: '确定要清空所有内容吗?',
|
||||||
attLimitMsg: '附件大小限制28mb',
|
attLimitMsg: '附件不能超过28MB',
|
||||||
emptyRecipientMsg: '收件人邮箱地址不能为空',
|
emptyRecipientMsg: '收件人邮箱地址不能为空',
|
||||||
emptySubjectMsg: '主题不能为空',
|
emptySubjectMsg: '主题不能为空',
|
||||||
emptyContentMsg: '邮件正文不能为空',
|
emptyContentMsg: '邮件正文不能为空',
|
||||||
@@ -307,6 +307,8 @@ const zh = {
|
|||||||
emailText: '邮件文本',
|
emailText: '邮件文本',
|
||||||
emailPrefix: '邮箱前缀',
|
emailPrefix: '邮箱前缀',
|
||||||
atLeast: '至少',
|
atLeast: '至少',
|
||||||
character: '位'
|
character: '位',
|
||||||
|
mustNotContain: '禁止包含',
|
||||||
|
mustNotContainDesc: '输入多个值用,分开'
|
||||||
}
|
}
|
||||||
export default zh
|
export default zh
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<el-menu-item @click="router.push({name: 'email'})" index="email"
|
<el-menu-item @click="router.push({name: 'email'})" index="email"
|
||||||
:class="route.meta.name === 'email' ? 'choose-item' : ''">
|
:class="route.meta.name === 'email' ? 'choose-item' : ''">
|
||||||
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
|
<Icon icon="hugeicons:mailbox-01" width="20" height="20" />
|
||||||
<span class="menu-name" style="margin-left: 21px">{{$t('收件箱')}}</span>
|
<span class="menu-name" style="margin-left: 21px">{{$t('inbox')}}</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item @click="router.push({name: 'send'})" index="send" v-perm="'email:send'"
|
<el-menu-item @click="router.push({name: 'send'})" index="send" v-perm="'email:send'"
|
||||||
:class="route.meta.name === 'send' ? 'choose-item' : ''">
|
:class="route.meta.name === 'send' ? 'choose-item' : ''">
|
||||||
@@ -96,8 +96,17 @@ const route = useRoute();
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background: linear-gradient(135deg, #1890ff, #3a80dd);
|
background: linear-gradient(135deg, #1890ff, #3a80dd);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
max-width: 240px;
|
||||||
|
padding: 0 10px;
|
||||||
|
> div {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: calc(240px - 20px - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-icon) {
|
:deep(.el-icon) {
|
||||||
|
flex-shrink: 0;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const routes = [
|
|||||||
name: 'email',
|
name: 'email',
|
||||||
component: () => import('@/views/email/index.vue'),
|
component: () => import('@/views/email/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '收件箱',
|
title: 'inbox',
|
||||||
name: 'email',
|
name: 'email',
|
||||||
menu: true
|
menu: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -713,15 +713,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog v-model="emailPrefixShow" :title="t('emailPrefix')" width="30" >
|
<el-dialog v-model="emailPrefixShow" :title="t('emailPrefix')" @closed="resetEmailPrefix" >
|
||||||
<div class="email-prefix">
|
<div class="email-prefix">
|
||||||
<div>{{ t('atLeast') }}</div>
|
<div>{{ t('atLeast') }}</div>
|
||||||
<el-input-number v-model="minEmailPrefix" :min="1" :max="20" @change="EmailPrefixChange" style="width: 150px" >
|
<el-input-number v-model="minEmailPrefix" :min="1" :max="20" style="width: 150px" >
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<span>{{ t('character') }}</span>
|
<span>{{ t('character') }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-input-number>
|
</el-input-number>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="prefix-filter">
|
||||||
|
<div style="margin-bottom: 10px;">{{ t('mustNotContain') }}</div>
|
||||||
|
<el-input-tag style="margin-bottom: 10px;" v-model="emailPrefixFilter" :placeholder="t('mustNotContainDesc')" />
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" style="width: 100%;" :loading="settingLoading" @click="saveEmailPrefix">{{ $t('save') }}</el-button>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
@@ -777,6 +782,7 @@ const clearS3Loading = ref(false)
|
|||||||
const r2DomainInput = ref('')
|
const r2DomainInput = ref('')
|
||||||
const loginOpacity = ref(0)
|
const loginOpacity = ref(0)
|
||||||
const minEmailPrefix = ref(0)
|
const minEmailPrefix = ref(0)
|
||||||
|
const emailPrefixFilter = ref([])
|
||||||
const backgroundUrl = ref('')
|
const backgroundUrl = ref('')
|
||||||
let backgroundFile = {}
|
let backgroundFile = {}
|
||||||
const showSetBackground = ref(false)
|
const showSetBackground = ref(false)
|
||||||
@@ -868,6 +874,7 @@ function getSettings() {
|
|||||||
regVerifyCount.value = setting.value.regVerifyCount
|
regVerifyCount.value = setting.value.regVerifyCount
|
||||||
resetNoticeForm()
|
resetNoticeForm()
|
||||||
resetAddS3Form()
|
resetAddS3Form()
|
||||||
|
resetEmailPrefix()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,16 +1144,17 @@ function doOpacityChange() {
|
|||||||
editSetting(form, true)
|
editSetting(form, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
function doEmailPrefix() {
|
function resetEmailPrefix() {
|
||||||
const form = {}
|
minEmailPrefix.value = setting.value.minEmailPrefix
|
||||||
form.minEmailPrefix = minEmailPrefix.value
|
emailPrefixFilter.value = setting.value.emailPrefixFilter
|
||||||
editSetting(form, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmailPrefixChange = debounce(doEmailPrefix, 1000, {
|
function saveEmailPrefix() {
|
||||||
leading: false,
|
const form = {}
|
||||||
trailing: true
|
form.minEmailPrefix = minEmailPrefix.value
|
||||||
})
|
form.emailPrefixFilter = emailPrefixFilter.value
|
||||||
|
editSetting(form, true)
|
||||||
|
}
|
||||||
|
|
||||||
const opacityChange = debounce(doOpacityChange, 1000, {
|
const opacityChange = debounce(doOpacityChange, 1000, {
|
||||||
leading: false,
|
leading: false,
|
||||||
@@ -1314,9 +1322,9 @@ function editSetting(settingForm, refreshStatus = true) {
|
|||||||
regVerifyCountShow.value = false
|
regVerifyCountShow.value = false
|
||||||
noticePopupShow.value = false
|
noticePopupShow.value = false
|
||||||
addS3Show.value = false
|
addS3Show.value = false
|
||||||
|
emailPrefixShow.value = false
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
loginOpacity.value = setting.value.loginOpacity
|
loginOpacity.value = setting.value.loginOpacity
|
||||||
minEmailPrefix.value = setting.value.minEmailPrefix
|
|
||||||
setting.value = {...setting.value, ...JSON.parse(backup)}
|
setting.value = {...setting.value, ...JSON.parse(backup)}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
settingLoading.value = false
|
settingLoading.value = false
|
||||||
@@ -1675,6 +1683,11 @@ function editSetting(settingForm, refreshStatus = true) {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prefix-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.s3-button {
|
.s3-button {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 80px 1fr;
|
grid-template-columns: 80px 1fr;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const setting = sqliteTable('setting', {
|
|||||||
tgMsgFrom: text('tg_msg_from').default('only-name').notNull(),
|
tgMsgFrom: text('tg_msg_from').default('only-name').notNull(),
|
||||||
tgMsgTo: text('tg_msg_to').default('show').notNull(),
|
tgMsgTo: text('tg_msg_to').default('show').notNull(),
|
||||||
tgMsgText: text('tg_msg_text').default('hide').notNull(),
|
tgMsgText: text('tg_msg_text').default('hide').notNull(),
|
||||||
minEmailPrefix: integer('min_email_prefix').default(0).notNull()
|
minEmailPrefix: integer('min_email_prefix').default(0).notNull(),
|
||||||
|
emailPrefixFilter: text('email_prefix_filter').default('').notNull()
|
||||||
});
|
});
|
||||||
export default setting
|
export default setting
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const en = {
|
|||||||
pwdLengthLimit: 'Password length exceeds the limit',
|
pwdLengthLimit: 'Password length exceeds the limit',
|
||||||
emailLengthLimit: 'Email length exceeds the limit',
|
emailLengthLimit: 'Email length exceeds the limit',
|
||||||
minEmailPrefix: 'Email must be at least {{msg}} characters',
|
minEmailPrefix: 'Email must be at least {{msg}} characters',
|
||||||
|
banEmailPrefix: 'Invalid characters in email address',
|
||||||
pwdMinLength: 'Password must be at least 6 characters',
|
pwdMinLength: 'Password must be at least 6 characters',
|
||||||
notEmailDomain: 'Invalid email domain',
|
notEmailDomain: 'Invalid email domain',
|
||||||
emptyRegKey: 'Invite code cannot be empty',
|
emptyRegKey: 'Invite code cannot be empty',
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ const zh = {
|
|||||||
notExistEmailReply: '邮件不存在无法回复',
|
notExistEmailReply: '邮件不存在无法回复',
|
||||||
pwdLengthLimit: '密码长度超出限制',
|
pwdLengthLimit: '密码长度超出限制',
|
||||||
emailLengthLimit: '邮箱长度超出限制',
|
emailLengthLimit: '邮箱长度超出限制',
|
||||||
minEmailPrefix: '邮箱名不能小于{{msg}}位',
|
minEmailPrefix: '邮箱名至少{{msg}}位',
|
||||||
pwdMinLength: '密码不能小于6位',
|
banEmailPrefix: '邮箱名包含非法字符',
|
||||||
|
pwdMinLength: '密码至少六位',
|
||||||
notEmailDomain: '非法邮箱域名',
|
notEmailDomain: '非法邮箱域名',
|
||||||
emptyRegKey: '注册码不能为空',
|
emptyRegKey: '注册码不能为空',
|
||||||
notExistRegKey: '注册码不存在',
|
notExistRegKey: '注册码不存在',
|
||||||
|
|||||||
@@ -24,10 +24,19 @@ const init = {
|
|||||||
await this.v2DB(c);
|
await this.v2DB(c);
|
||||||
await this.v2_3DB(c);
|
await this.v2_3DB(c);
|
||||||
await this.v2_4DB(c);
|
await this.v2_4DB(c);
|
||||||
|
await this.v2_5DB(c);
|
||||||
await settingService.refresh(c);
|
await settingService.refresh(c);
|
||||||
return c.text(t('initSuccess'));
|
return c.text(t('initSuccess'));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async v2_5DB(c) {
|
||||||
|
try {
|
||||||
|
await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN email_prefix_filter text NOT NULL DEFAULT '';`).run();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async v2_4DB(c) {
|
async v2_4DB(c) {
|
||||||
try {
|
try {
|
||||||
await c.env.db.prepare(`
|
await c.env.db.prepare(`
|
||||||
@@ -54,6 +63,7 @@ const init = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async v2_3DB(c) {
|
async v2_3DB(c) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const accountService = {
|
|||||||
|
|
||||||
async add(c, params, userId) {
|
async add(c, params, userId) {
|
||||||
|
|
||||||
const {addEmailVerify , addEmail, manyEmail, addVerifyCount, minEmailPrefix} = await settingService.query(c);
|
const { addEmailVerify , addEmail, manyEmail, addVerifyCount, minEmailPrefix, emailPrefixFilter } = await settingService.query(c);
|
||||||
|
|
||||||
let { email, token } = params;
|
let { email, token } = params;
|
||||||
|
|
||||||
@@ -43,6 +43,10 @@ const accountService = {
|
|||||||
throw new BizError(t('minEmailPrefix', { msg: minEmailPrefix } ));
|
throw new BizError(t('minEmailPrefix', { msg: minEmailPrefix } ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emailPrefixFilter.some(content => emailUtils.getName(email).includes(content))) {
|
||||||
|
throw new BizError(t('banEmailPrefix'));
|
||||||
|
}
|
||||||
|
|
||||||
let accountRow = await this.selectByEmailIncludeDel(c, email);
|
let accountRow = await this.selectByEmailIncludeDel(c, email);
|
||||||
|
|
||||||
if (accountRow && accountRow.isDel === isDel.DELETE) {
|
if (accountRow && accountRow.isDel === isDel.DELETE) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const loginService = {
|
|||||||
|
|
||||||
const { email, password, token, code } = params;
|
const { email, password, token, code } = params;
|
||||||
|
|
||||||
let {regKey, register, registerVerify, regVerifyCount, minEmailPrefix} = await settingService.query(c)
|
let { regKey, register, registerVerify, regVerifyCount, minEmailPrefix, emailPrefixFilter } = await settingService.query(c)
|
||||||
|
|
||||||
if (oauth) {
|
if (oauth) {
|
||||||
registerVerify = settingConst.registerVerify.CLOSE;
|
registerVerify = settingConst.registerVerify.CLOSE;
|
||||||
@@ -45,6 +45,10 @@ const loginService = {
|
|||||||
throw new BizError(t('minEmailPrefix', { msg: minEmailPrefix } ));
|
throw new BizError(t('minEmailPrefix', { msg: minEmailPrefix } ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emailPrefixFilter.some(content => emailUtils.getName(email).includes(content))) {
|
||||||
|
throw new BizError(t('banEmailPrefix'));
|
||||||
|
}
|
||||||
|
|
||||||
if (emailUtils.getName(email).length > 64) {
|
if (emailUtils.getName(email).length > 64) {
|
||||||
throw new BizError(t('emailLengthLimit'));
|
throw new BizError(t('emailLengthLimit'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ const settingService = {
|
|||||||
setting.linuxdoCallbackUrl = c.env.linuxdo_callback_url;
|
setting.linuxdoCallbackUrl = c.env.linuxdo_callback_url;
|
||||||
setting.linuxdoSwitch = linuxdoSwitch;
|
setting.linuxdoSwitch = linuxdoSwitch;
|
||||||
|
|
||||||
|
setting.emailPrefixFilter = setting.emailPrefixFilter.split(",").filter(Boolean);
|
||||||
|
|
||||||
c.set?.('setting', setting);
|
c.set?.('setting', setting);
|
||||||
return setting;
|
return setting;
|
||||||
},
|
},
|
||||||
@@ -108,6 +110,11 @@ const settingService = {
|
|||||||
Object.keys(resendTokens).forEach(domain => {
|
Object.keys(resendTokens).forEach(domain => {
|
||||||
if (!resendTokens[domain]) delete resendTokens[domain];
|
if (!resendTokens[domain]) delete resendTokens[domain];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Array.isArray(params.emailPrefixFilter)) {
|
||||||
|
params.emailPrefixFilter = params.emailPrefixFilter + '';
|
||||||
|
}
|
||||||
|
|
||||||
params.resendTokens = JSON.stringify(resendTokens);
|
params.resendTokens = JSON.stringify(resendTokens);
|
||||||
await orm(c).update(setting).set({ ...params }).returning().get();
|
await orm(c).update(setting).set({ ...params }).returning().get();
|
||||||
await this.refresh(c);
|
await this.refresh(c);
|
||||||
|
|||||||
Reference in New Issue
Block a user