diff --git a/mail-vue/src/components/email-scroll/index.vue b/mail-vue/src/components/email-scroll/index.vue index 13fba5a..e84eb1d 100644 --- a/mail-vue/src/components/email-scroll/index.vue +++ b/mail-vue/src/components/email-scroll/index.vue @@ -12,7 +12,7 @@ - diff --git a/mail-vue/src/i18n/en.js b/mail-vue/src/i18n/en.js index a0b0b23..938a838 100644 --- a/mail-vue/src/i18n/en.js +++ b/mail-vue/src/i18n/en.js @@ -191,6 +191,7 @@ const en = { delSuccessMsg: 'Deleted successfully', emptyEmailMsg: 'Email cannot be empty', notEmailMsg: 'Invalid email', + minEmailPrefix: 'Email must be at least {msg} characters', emptyPwdMsg: 'Password cannot be empty', pwdLengthMsg: 'Password must be at least 6 characters', confirmPwdFailMsg: 'The two passwords do not match', @@ -303,7 +304,10 @@ const en = { show: 'Show', hide: 'Hide', onlyName: 'Only name', - emailText: 'Email Text' + emailText: 'Email Text', + emailPrefix: 'Email Prefix', + atLeast: 'At Least', + character: '' } export default en diff --git a/mail-vue/src/i18n/zh.js b/mail-vue/src/i18n/zh.js index 14b7ff1..bf83140 100644 --- a/mail-vue/src/i18n/zh.js +++ b/mail-vue/src/i18n/zh.js @@ -193,6 +193,7 @@ const zh = { notEmailMsg: '输入的邮箱不合法', emptyPwdMsg: '密码不能为空', pwdLengthMsg: '密码最少六位', + minEmailPrefix: '邮箱名不能小于{msg}位', confirmPwdFailMsg: '两次密码输入不一致', emptyRegKeyMsg: '注册码不能为空', regSuccessMsg: '注册成功', @@ -303,6 +304,9 @@ const zh = { show: '显示', hide: '隐藏', onlyName: '仅名字', - emailText: '邮件文本' + emailText: '邮件文本', + emailPrefix: '邮箱前缀', + atLeast: '至少', + character: '位' } export default zh diff --git a/mail-vue/src/icons/index.js b/mail-vue/src/icons/index.js index 1269eaa..b2cdeaf 100644 --- a/mail-vue/src/icons/index.js +++ b/mail-vue/src/icons/index.js @@ -646,3 +646,40 @@ addCollection({ } } }) +addCollection({ + "prefix": "mingcute", + "lastModified": 1754900188, + "aliases": {}, + "width": 24, + "height": 24, + "icons": { + "github-line": { + "body": "" + } + } +}) +addCollection({ + "prefix": "hugeicons", + "lastModified": 1759033396, + "aliases": {}, + "width": 24, + "height": 24, + "icons": { + "view": { + "body": "" + } + } +}) +addCollection({ + "prefix": "bitcoin-icons", + "lastModified": 1754898649, + "aliases": {}, + "width": 24, + "height": 24, + "icons": { + "refresh-filled": { + "body": "" + } + } +}) + diff --git a/mail-vue/src/layout/account/index.vue b/mail-vue/src/layout/account/index.vue index 7c3288d..80e5517 100644 --- a/mail-vue/src/layout/account/index.vue +++ b/mail-vue/src/layout/account/index.vue @@ -377,6 +377,15 @@ function submit() { return } + if (addForm.email.length < settingStore.settings.minEmailPrefix) { + ElMessage({ + message: t('minEmailPrefix', {msg: settingStore.settings.minEmailPrefix}), + type: 'error', + plain: true, + }) + return + } + if (!isEmail(addForm.email + addForm.suffix)) { ElMessage({ message: t('notEmailMsg'), diff --git a/mail-vue/src/views/all-email/index.vue b/mail-vue/src/views/all-email/index.vue index ea8c25e..cf3d9d8 100644 --- a/mail-vue/src/views/all-email/index.vue +++ b/mail-vue/src/views/all-email/index.vue @@ -397,7 +397,7 @@ function getEmailList(emailId, size) { } .clear { - @media (max-width: 407px) { + @media (max-width: 409px) { position: absolute; top: 41px; left: 242px; @@ -405,10 +405,18 @@ function getEmailList(emailId, size) { } :deep(.reload) { - @media (max-width: 407px) { + @media (max-width: 409px) { position: absolute; top: 42px; left: 208px; } } + +:deep(.delete) { + @media (max-width: 443px) { + position: absolute; + top: 43px; + left: 284px; + } +} diff --git a/mail-vue/src/views/login/index.vue b/mail-vue/src/views/login/index.vue index 53bffad..dc7d953 100644 --- a/mail-vue/src/views/login/index.vue +++ b/mail-vue/src/views/login/index.vue @@ -304,6 +304,16 @@ function bind() { return } + + if (bindForm.email.length < settingStore.settings.minEmailPrefix) { + ElMessage({ + message: t('minEmailPrefix', {msg: settingStore.settings.minEmailPrefix}), + type: 'error', + plain: true, + }) + return + } + let email = bindForm.email + suffix.value; @@ -407,6 +417,17 @@ function submitRegister() { return } + console.log(registerForm.email) + + if (registerForm.email.length < settingStore.settings.minEmailPrefix) { + ElMessage({ + message: t('minEmailPrefix', {msg: settingStore.settings.minEmailPrefix}), + type: 'error', + plain: true, + }) + return + } + if (!isEmail(registerForm.email + suffix.value)) { ElMessage({ message: t('notEmailMsg'), diff --git a/mail-vue/src/views/sys-setting/index.vue b/mail-vue/src/views/sys-setting/index.vue index 06ead2b..d990946 100644 --- a/mail-vue/src/views/sys-setting/index.vue +++ b/mail-vue/src/views/sys-setting/index.vue @@ -61,7 +61,16 @@ v-model="setting.manyEmail"/> - +
+
+ {{ $t('emailPrefix') }} +
+
+ + + +
+
@@ -349,7 +358,7 @@
{{ $t('version') }} : - + {{ currentVersion }} @@ -730,7 +749,7 @@ defineOptions({ name: 'sys-setting' }) -const currentVersion = 'v2.3.0' +const currentVersion = 'v2.4.0' const hasUpdate = ref(false) let getUpdateErrorCount = 1; const {t, locale} = useI18n(); @@ -747,6 +766,7 @@ const tgSettingShow = ref(false) const noticePopupShow = ref(false) const thirdEmailShow = ref(false) const forwardRulesShow = ref(false) +const emailPrefixShow = ref(false) const showResendList = ref(false) const settingStore = useSettingStore(); const uiStore = useUiStore(); @@ -756,6 +776,7 @@ const settingLoading = ref(false) const clearS3Loading = ref(false) const r2DomainInput = ref('') const loginOpacity = ref(0) +const minEmailPrefix = ref(0) const backgroundUrl = ref('') let backgroundFile = {} const showSetBackground = ref(false) @@ -838,6 +859,7 @@ function getSettings() { settingStore.domainList = settingData.domainList; resendTokenForm.domain = setting.value.domainList[0] loginOpacity.value = setting.value.loginOpacity + minEmailPrefix.value = setting.value.minEmailPrefix firstLoading.value = false backgroundUrl.value = setting.value.background?.startsWith('http') ? setting.value.background : '' editTitle.value = setting.value.title @@ -897,7 +919,7 @@ const resendList = computed(() => { function getUpdate() { if (getUpdateErrorCount > 5 || !getUpdateErrorCount) return - axios.get('https://api.github.com/repos/eoao/cloud-mail/releases/latest').then(({data}) => { + axios.get('https://api.github.com/repos/maillab/cloud-mail/releases/latest').then(({data}) => { hasUpdate.value = data.name !== currentVersion getUpdateErrorCount = 0 }).catch(e => { @@ -993,6 +1015,10 @@ function openThirdEmailSetting() { thirdEmailShow.value = true } +function openEmailPrefix() { + emailPrefixShow.value = true +} + function openForwardRules() { ruleType.value = setting.value.ruleType ruleEmail.value = [] @@ -1111,6 +1137,17 @@ function doOpacityChange() { editSetting(form, true) } +function doEmailPrefix() { + const form = {} + form.minEmailPrefix = minEmailPrefix.value + editSetting(form, true) +} + +const EmailPrefixChange = debounce(doEmailPrefix, 1000, { + leading: false, + trailing: true +}) + const opacityChange = debounce(doOpacityChange, 1000, { leading: false, trailing: true @@ -1279,6 +1316,7 @@ function editSetting(settingForm, refreshStatus = true) { addS3Show.value = false }).catch((e) => { loginOpacity.value = setting.value.loginOpacity + minEmailPrefix.value = setting.value.minEmailPrefix setting.value = {...setting.value, ...JSON.parse(backup)} }).finally(() => { settingLoading.value = false @@ -1350,13 +1388,13 @@ function editSetting(settingForm, refreshStatus = true) { } .background { - width: 230px; - height: 120px; + width: 249px; + height: 140px; border-radius: 4px; border: 1px solid var(--light-border); @media (max-width: 500px) { - width: 150px; - height: 83px; + width: 160px; + height: 90px; } } @@ -1632,6 +1670,11 @@ function editSetting(settingForm, refreshStatus = true) { width: fit-content !important; } +.email-prefix { + display: flex; + justify-content: space-between; +} + .s3-button { display: grid; grid-template-columns: 80px 1fr; diff --git a/mail-worker/src/entity/setting.js b/mail-worker/src/entity/setting.js index fc4db1b..7219aab 100644 --- a/mail-worker/src/entity/setting.js +++ b/mail-worker/src/entity/setting.js @@ -45,6 +45,7 @@ export const setting = sqliteTable('setting', { customDomain: text('custom_domain').default('').notNull(), tgMsgFrom: text('tg_msg_from').default('only-name').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() }); export default setting diff --git a/mail-worker/src/i18n/en.js b/mail-worker/src/i18n/en.js index b69a0e2..30a6f46 100644 --- a/mail-worker/src/i18n/en.js +++ b/mail-worker/src/i18n/en.js @@ -27,7 +27,8 @@ const en = { notExistEmailReply: 'Mail does not exist and cannot be replied to', pwdLengthLimit: 'Password length exceeds the limit', emailLengthLimit: 'Email length exceeds the limit', - pwdMinLengthLimit: 'Password must be at least 6 characters', + minEmailPrefix: 'Email must be at least {{msg}} characters', + pwdMinLength: 'Password must be at least 6 characters', notEmailDomain: 'Invalid email domain', emptyRegKey: 'Invite code cannot be empty', notExistRegKey: 'Invite code does not exist', diff --git a/mail-worker/src/i18n/i18n.js b/mail-worker/src/i18n/i18n.js index 5fdfdf2..a90393e 100644 --- a/mail-worker/src/i18n/i18n.js +++ b/mail-worker/src/i18n/i18n.js @@ -25,6 +25,6 @@ i18next.init({ resources, }); -export const t = (key) => i18next.t(key) +export const t = (key, values) => i18next.t(key, values) export default i18next; diff --git a/mail-worker/src/i18n/zh.js b/mail-worker/src/i18n/zh.js index ffda96f..a963aa9 100644 --- a/mail-worker/src/i18n/zh.js +++ b/mail-worker/src/i18n/zh.js @@ -27,7 +27,8 @@ const zh = { notExistEmailReply: '邮件不存在无法回复', pwdLengthLimit: '密码长度超出限制', emailLengthLimit: '邮箱长度超出限制', - pwdMinLengthLimit: '密码不能小于6位', + minEmailPrefix: '邮箱名不能小于{{msg}}位', + pwdMinLength: '密码不能小于6位', notEmailDomain: '非法邮箱域名', emptyRegKey: '注册码不能为空', notExistRegKey: '注册码不存在', diff --git a/mail-worker/src/init/init.js b/mail-worker/src/init/init.js index 693a0c0..a70fb2c 100644 --- a/mail-worker/src/init/init.js +++ b/mail-worker/src/init/init.js @@ -29,20 +29,31 @@ const init = { }, async v2_4DB(c) { - await c.env.db.prepare(` - CREATE TABLE IF NOT EXISTS oauth ( - oauth_id INTEGER PRIMARY KEY AUTOINCREMENT, - oauth_user_id TEXT, - username TEXT, - name TEXT, - avatar TEXT, - active INTEGER, - trust_level INTEGER, - silenced INTEGER, - create_time DATETIME DEFAULT CURRENT_TIMESTAMP, - user_id INTEGER NOT NULL DEFAULT 0 - ) - `).run(); + try { + await c.env.db.prepare(` + CREATE TABLE IF NOT EXISTS oauth ( + oauth_id INTEGER PRIMARY KEY AUTOINCREMENT, + oauth_user_id TEXT, + username TEXT, + name TEXT, + avatar TEXT, + active INTEGER, + trust_level INTEGER, + silenced INTEGER, + create_time DATETIME DEFAULT CURRENT_TIMESTAMP, + platform INTEGER NOT NULL DEFAULT 0, + user_id INTEGER NOT NULL DEFAULT 0 + ) + `).run(); + } catch (e) { + console.error(e) + } + + try { + await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN min_email_prefix INTEGER NOT NULL DEFAULT 1;`).run(); + } catch (e) { + console.error(e) + } }, async v2_3DB(c) { @@ -59,7 +70,7 @@ const init = { } try { - await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN tg_msg_text TEXT NOT NULL DEFAULT 'hide';`).run(); + await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN tg_msg_text TEXT NOT NULL DEFAULT 'show';`).run(); } catch (e) { console.error(e) } diff --git a/mail-worker/src/service/account-service.js b/mail-worker/src/service/account-service.js index 66fb50a..4e40ffd 100644 --- a/mail-worker/src/service/account-service.js +++ b/mail-worker/src/service/account-service.js @@ -17,7 +17,7 @@ const accountService = { async add(c, params, userId) { - const {addEmailVerify , addEmail, manyEmail, addVerifyCount} = await settingService.query(c); + const {addEmailVerify , addEmail, manyEmail, addVerifyCount, minEmailPrefix} = await settingService.query(c); let { email, token } = params; @@ -39,6 +39,9 @@ const accountService = { throw new BizError(t('notExistDomain')); } + if (emailUtils.getName(email).length < minEmailPrefix) { + throw new BizError(t('minEmailPrefix', { msg: minEmailPrefix } )); + } let accountRow = await this.selectByEmailIncludeDel(c, email); diff --git a/mail-worker/src/service/login-service.js b/mail-worker/src/service/login-service.js index acff013..6cc21d8 100644 --- a/mail-worker/src/service/login-service.js +++ b/mail-worker/src/service/login-service.js @@ -26,7 +26,7 @@ const loginService = { const { email, password, token, code } = params; - let {regKey, register, registerVerify, regVerifyCount} = await settingService.query(c) + let {regKey, register, registerVerify, regVerifyCount, minEmailPrefix} = await settingService.query(c) if (oauth) { registerVerify = settingConst.registerVerify.CLOSE; @@ -41,16 +41,20 @@ const loginService = { throw new BizError(t('notEmail')); } + if (emailUtils.getName(email).length < minEmailPrefix) { + throw new BizError(t('minEmailPrefix', { msg: minEmailPrefix } )); + } + + if (emailUtils.getName(email).length > 64) { + throw new BizError(t('emailLengthLimit')); + } + if (password.length > 30) { throw new BizError(t('pwdLengthLimit')); } - if (emailUtils.getName(email).length > 30) { - throw new BizError(t('emailLengthLimit')); - } - if (password.length < 6) { - throw new BizError(t('pwdMinLengthLimit')); + throw new BizError(t('pwdMinLength')); } if (!c.env.domain.includes(emailUtils.getDomain(email))) { diff --git a/mail-worker/src/service/setting-service.js b/mail-worker/src/service/setting-service.js index de38a18..3f70e46 100644 --- a/mail-worker/src/service/setting-service.js +++ b/mail-worker/src/service/setting-service.js @@ -206,7 +206,8 @@ const settingService = { loginDomain: settingRow.loginDomain, linuxdoClientId: settingRow.linuxdoClientId, linuxdoCallbackUrl: settingRow.linuxdoCallbackUrl, - linuxdoSwitch: settingRow.linuxdoSwitch + linuxdoSwitch: settingRow.linuxdoSwitch, + minEmailPrefix: settingRow.minEmailPrefix }; } }; diff --git a/mail-worker/src/service/telegram-service.js b/mail-worker/src/service/telegram-service.js index 1b76f17..aaf5151 100644 --- a/mail-worker/src/service/telegram-service.js +++ b/mail-worker/src/service/telegram-service.js @@ -12,6 +12,7 @@ import emailMsgTemplate from '../template/email-msg'; import emailTextTemplate from '../template/email-text'; import emailHtmlTemplate from '../template/email-html'; import verifyUtils from '../utils/verify-utils'; +import domainUtils from "../utils/domain-uitls"; const telegramService = { @@ -50,7 +51,7 @@ const telegramService = { const jwtToken = await jwtUtils.generateToken(c, { emailId: email.emailId }) - const webAppUrl = verifyUtils.isDomain(customDomain) ? `https://${customDomain}/api/telegram/getEmail/${jwtToken}` : 'https://www.cloudflare.com/404' + const webAppUrl = customDomain ? `${domainUtils.toOssDomain(customDomain)}/api/telegram/getEmail/${jwtToken}` : 'https://www.cloudflare.com/404' await Promise.all(tgChatIds.map(async chatId => { try { diff --git a/mail-worker/src/service/user-service.js b/mail-worker/src/service/user-service.js index 129f4ac..2d7468b 100644 --- a/mail-worker/src/service/user-service.js +++ b/mail-worker/src/service/user-service.js @@ -53,7 +53,7 @@ const userService = { const { password } = params; if (password < 6) { - throw new BizError(t('pwdMinLengthLimit')); + throw new BizError(t('pwdMinLength')); } const { salt, hash } = await cryptoUtils.hashPassword(password); await orm(c).update(user).set({ password: hash, salt: salt }).where(eq(user.userId, userId)).run(); @@ -304,7 +304,7 @@ const userService = { } if (password.length < 6) { - throw new BizError(t('pwdMinLengthLimit')); + throw new BizError(t('pwdMinLength')); } const accountRow = await accountService.selectByEmailIncludeDel(c, email);