diff --git a/frontend/src/views/Admin.vue b/frontend/src/views/Admin.vue index 40318747..7952a685 100644 --- a/frontend/src/views/Admin.vue +++ b/frontend/src/views/Admin.vue @@ -14,6 +14,7 @@ import AccountSettings from './admin/AccountSettings.vue'; import UserManagement from './admin/UserManagement.vue'; import UserSettings from './admin/UserSettings.vue'; import UserOauth2Settings from './admin/UserOauth2Settings.vue'; +import RoleAddressConfig from './admin/RoleAddressConfig.vue'; import Mails from './admin/Mails.vue'; import MailsUnknow from './admin/MailsUnknow.vue'; import About from './common/About.vue'; @@ -61,6 +62,7 @@ const { t } = useI18n({ user_management: 'User Management', user_settings: 'User Settings', userOauth2Settings: 'Oauth2 Settings', + roleAddressConfig: 'Role Address Config', unknow: 'Mails with unknow receiver', senderAccess: 'Sender Access Control', sendBox: 'Send Box', @@ -88,6 +90,7 @@ const { t } = useI18n({ user_management: '用户管理', user_settings: '用户设置', userOauth2Settings: 'Oauth2 设置', + roleAddressConfig: '角色地址配置', unknow: '无收件人邮件', senderAccess: '发件权限控制', sendBox: '发件箱', @@ -173,6 +176,9 @@ onMounted(async () => { + + + diff --git a/frontend/src/views/admin/RoleAddressConfig.vue b/frontend/src/views/admin/RoleAddressConfig.vue new file mode 100644 index 00000000..7c24b7bd --- /dev/null +++ b/frontend/src/views/admin/RoleAddressConfig.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/worker/src/admin_api/admin_user_api.ts b/worker/src/admin_api/admin_user_api.ts index b1aa24ba..99a8b40a 100644 --- a/worker/src/admin_api/admin_user_api.ts +++ b/worker/src/admin_api/admin_user_api.ts @@ -2,7 +2,7 @@ import { Context } from 'hono'; import { CONSTANTS } from '../constants'; import { getJsonSetting, saveSetting, checkUserPassword, getDomains, getUserRoles } from '../utils'; -import { UserSettings, GeoData, UserInfo } from "../models"; +import { UserSettings, GeoData, UserInfo, RoleAddressConfig } from "../models"; import { handleListQuery } from '../common' import UserBindAddressModule from '../user_api/bind_address'; import i18n from '../i18n'; @@ -166,4 +166,14 @@ export default { results: results, }); }, + getRoleAddressConfig: async (c: Context) => { + const value = await getJsonSetting(c, CONSTANTS.ROLE_ADDRESS_CONFIG_KEY); + const configs = value || {}; + return c.json({ configs }); + }, + saveRoleAddressConfig: async (c: Context) => { + const { configs } = await c.req.json<{ configs: RoleAddressConfig }>(); + await saveSetting(c, CONSTANTS.ROLE_ADDRESS_CONFIG_KEY, JSON.stringify(configs)); + return c.json({ success: true }); + }, } diff --git a/worker/src/admin_api/index.ts b/worker/src/admin_api/index.ts index ff0139db..4d974f61 100644 --- a/worker/src/admin_api/index.ts +++ b/worker/src/admin_api/index.ts @@ -344,6 +344,8 @@ api.post('/admin/users', admin_user_api.createUser) api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword) api.get('/admin/user_roles', async (c) => c.json(getUserRoles(c))) api.post('/admin/user_roles', admin_user_api.updateUserRoles) +api.get('/admin/role_address_config', admin_user_api.getRoleAddressConfig) +api.post('/admin/role_address_config', admin_user_api.saveRoleAddressConfig) api.get('/admin/users/bind_address/:user_id', admin_user_api.getBindedAddresses) api.post('/admin/users/bind_address', admin_user_api.bindAddress) diff --git a/worker/src/constants.ts b/worker/src/constants.ts index 4b612996..10f29e77 100644 --- a/worker/src/constants.ts +++ b/worker/src/constants.ts @@ -14,6 +14,7 @@ export const CONSTANTS = { VERIFIED_ADDRESS_LIST_KEY: 'verified_address_list', NO_LIMIT_SEND_ADDRESS_LIST_KEY: 'no_limit_send_address_list', EMAIL_RULE_SETTINGS_KEY: 'email_rule_settings', + ROLE_ADDRESS_CONFIG_KEY: 'role_address_config', // KV TG_KV_PREFIX: "temp-mail-telegram", diff --git a/worker/src/models/index.ts b/worker/src/models/index.ts index a6049292..0f8908ab 100644 --- a/worker/src/models/index.ts +++ b/worker/src/models/index.ts @@ -154,3 +154,10 @@ export type EmailRuleSettings = { blockReceiveUnknowAddressEmail: boolean; emailForwardingList: SubdomainForwardAddressList[] } + +export type RoleConfig = { + maxAddressCount?: number; + // future configs can be added here +} + +export type RoleAddressConfig = Record; diff --git a/worker/src/user_api/bind_address.ts b/worker/src/user_api/bind_address.ts index 7ece5d32..389a590f 100644 --- a/worker/src/user_api/bind_address.ts +++ b/worker/src/user_api/bind_address.ts @@ -1,12 +1,26 @@ import { Context } from 'hono'; import { Jwt } from 'hono/utils/jwt' -import { UserSettings } from "../models"; +import { UserSettings, RoleAddressConfig } from "../models"; import { getJsonSetting } from "../utils" import { CONSTANTS } from "../constants"; import { unbindTelegramByAddress } from '../telegram_api/common'; import i18n from '../i18n'; -import { updateAddressUpdatedAt } from '../common'; +import { updateAddressUpdatedAt, commonGetUserRole } from '../common'; + +const getMaxAddressCount = async ( + c: Context, + userRole: string | null | undefined, + settings: UserSettings +): Promise => { + if (!userRole) return settings.maxAddressCount; + const roleConfigs = await getJsonSetting(c, CONSTANTS.ROLE_ADDRESS_CONFIG_KEY); + if (!roleConfigs) return settings.maxAddressCount; + const roleMaxCount = roleConfigs[userRole]?.maxAddressCount; + if (typeof roleMaxCount !== 'number') return settings.maxAddressCount; + if (roleMaxCount <= 0) return settings.maxAddressCount; + return roleMaxCount; +}; const UserBindAddressModule = { bind: async (c: Context) => { @@ -43,11 +57,15 @@ const UserBindAddressModule = { // check if binded address count const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); const settings = new UserSettings(value); - if (settings.maxAddressCount > 0) { + // get user role + const userRole = c.get("userRolePayload"); + // check role-based max address count first, fallback to global settings + const maxAddressCount = await getMaxAddressCount(c, userRole, settings); + if (maxAddressCount > 0) { const { count } = await c.env.DB.prepare( `SELECT COUNT(*) as count FROM users_address where user_id = ?` ).bind(user_id).first<{ count: number }>() || { count: 0 }; - if (count >= settings.maxAddressCount) { + if (count >= maxAddressCount) { return c.text("Max address count reached", 400) } } @@ -194,18 +212,22 @@ const UserBindAddressModule = { // check if target user exists const target_user_id = await c.env.DB.prepare( `SELECT id FROM users where user_email = ?` - ).bind(target_user_email).first("id"); + ).bind(target_user_email).first("id"); if (!target_user_id) { return c.text("Target user not found", 400) } // check target user binded address count const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); const settings = new UserSettings(value); - if (settings.maxAddressCount > 0) { + // get target user role + const userRoleObj = await commonGetUserRole(c, target_user_id); + // check role-based max address count first, fallback to global settings + const maxAddressCount = await getMaxAddressCount(c, userRoleObj?.role, settings); + if (maxAddressCount > 0) { const { count } = await c.env.DB.prepare( `SELECT COUNT(*) as count FROM users_address where user_id = ?` ).bind(target_user_id).first<{ count: number }>() || { count: 0 }; - if (count >= settings.maxAddressCount) { + if (count >= maxAddressCount) { return c.text("Target User Max address count reached", 400) } }