diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46cfabfc..9698f91c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,21 @@
🇺🇸 English
-## v1.2.1(main)
+## v1.3.0(main)
+
+### Features
+
+- feat: |OAuth2| 新增 OAuth2 邮箱格式转换功能,支持通过正则表达式转换第三方登录返回的邮箱格式(如将 `user@domain` 转换为 `user@custom.domain`)
+
+### Bug Fixes
+
+- fix: |用户地址| 修复禁止匿名创建时,已登录用户地址数量限制检查失效的问题,新增公共函数 `isAddressCountLimitReached` 统一处理地址数量限制逻辑
+
+### Improvements
+
+- refactor: |代码重构| 提取地址数量限制检查为公共函数,优化代码复用性
+
+## v1.2.1
### Bug Fixes
diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md
index 5db79727..25860158 100644
--- a/CHANGELOG_EN.md
+++ b/CHANGELOG_EN.md
@@ -6,7 +6,21 @@
🇺🇸 English
-## v1.2.1(main)
+## v1.3.0(main)
+
+### Features
+
+- feat: |OAuth2| Add email format transformation support for OAuth2, allowing regex-based email format conversion from third-party login providers (e.g., transform `user@domain` to `user@custom.domain`)
+
+### Bug Fixes
+
+- fix: |User Address| Fix address count limit check failure when anonymous creation is disabled for logged-in users, add public function `isAddressCountLimitReached` to unify address count limit logic
+
+### Improvements
+
+- refactor: |Code Refactoring| Extract address count limit check as a public function to improve code reusability
+
+## v1.2.1
### Bug Fixes
diff --git a/worker/src/mails_api/index.ts b/worker/src/mails_api/index.ts
index a7889d4e..02e641d8 100644
--- a/worker/src/mails_api/index.ts
+++ b/worker/src/mails_api/index.ts
@@ -1,7 +1,7 @@
import { Context, Hono } from 'hono'
import i18n from '../i18n';
-import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue, getSplitStringListValue } from '../utils';
+import { getBooleanValue, getJsonSetting, checkCfTurnstile, getStringValue, getSplitStringListValue, isAddressCountLimitReached } from '../utils';
import { newAddress, handleListQuery, deleteAddressWithData, getAddressPrefix, getAllowDomains, updateAddressUpdatedAt, generateRandomName } from '../common'
import { CONSTANTS } from '../constants'
import auto_reply from './auto_reply'
@@ -105,14 +105,25 @@ api.get('/api/settings', async (c) => {
api.post('/api/new_address', async (c) => {
const msgs = i18n.getMessagesbyContext(c);
+ const userPayload = c.get("userPayload");
+
if (getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL)
- && !c.get("userPayload")
+ && !userPayload
) {
return c.text(msgs.NewAddressAnonymousDisabledMsg, 403)
}
if (!getBooleanValue(c.env.ENABLE_USER_CREATE_EMAIL)) {
return c.text(msgs.NewAddressDisabledMsg, 403)
}
+
+ // 如果启用了禁止匿名创建,且用户已登录,检查地址数量限制
+ if (getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL) && userPayload) {
+ const userRole = c.get("userRolePayload");
+ if (await isAddressCountLimitReached(c, userPayload.user_id, userRole)) {
+ return c.text(msgs.MaxAddressCountReachedMsg, 400)
+ }
+ }
+
// eslint-disable-next-line prefer-const
let { name, domain, cf_token } = await c.req.json();
// check cf turnstile
diff --git a/worker/src/user_api/bind_address.ts b/worker/src/user_api/bind_address.ts
index 78405f38..848ae50f 100644
--- a/worker/src/user_api/bind_address.ts
+++ b/worker/src/user_api/bind_address.ts
@@ -1,27 +1,11 @@
import { Context } from 'hono';
import { Jwt } from 'hono/utils/jwt'
-import { UserSettings, RoleAddressConfig } from "../models";
-import { getJsonSetting } from "../utils"
-import { CONSTANTS } from "../constants";
+import { isAddressCountLimitReached } from "../utils"
import { unbindTelegramByAddress } from '../telegram_api/common';
import i18n from '../i18n';
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) => {
const { user_id } = c.get("userPayload");
@@ -56,19 +40,9 @@ const UserBindAddressModule = {
).bind(user_id, address_id).first("user_id");
if (db_user_address_id) return c.json({ success: true })
// check if binded address count
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value);
- // 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 >= maxAddressCount) {
- return c.text(msgs.MaxAddressCountReachedMsg, 400)
- }
+ if (await isAddressCountLimitReached(c, user_id, userRole)) {
+ return c.text(msgs.MaxAddressCountReachedMsg, 400)
}
// bind
try {
@@ -221,19 +195,9 @@ const UserBindAddressModule = {
return c.text(msgs.TargetUserNotFoundMsg, 400)
}
// check target user binded address count
- const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
- const settings = new UserSettings(value);
- // 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 >= maxAddressCount) {
- return c.text(msgs.MaxAddressCountReachedMsg, 400)
- }
+ if (await isAddressCountLimitReached(c, target_user_id, userRoleObj?.role)) {
+ return c.text(msgs.MaxAddressCountReachedMsg, 400)
}
// check if binded
const db_user_address_id = await c.env.DB.prepare(
diff --git a/worker/src/utils.ts b/worker/src/utils.ts
index 8d06092a..73541fba 100644
--- a/worker/src/utils.ts
+++ b/worker/src/utils.ts
@@ -1,5 +1,7 @@
import { Context } from "hono";
import { createMimeMessage } from "mimetext";
+import { UserSettings, RoleAddressConfig } from "./models";
+import { CONSTANTS } from "./constants";
export const getJsonObjectValue = (
value: string | any
@@ -303,6 +305,45 @@ export const hashPassword = async (password: string): Promise => {
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
}
+export 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;
+};
+
+/**
+ * 检查用户是否已达到地址数量限制
+ * @param c - Hono Context
+ * @param user_id - 用户 ID
+ * @param userRole - 用户角色
+ * @returns true 表示已超限,false 表示未超限
+ */
+export const isAddressCountLimitReached = async (
+ c: Context,
+ user_id: number | string,
+ userRole: string | null | undefined
+): Promise => {
+ const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY);
+ const settings = new UserSettings(value);
+ const maxAddressCount = await getMaxAddressCount(c, userRole, settings);
+
+ if (maxAddressCount <= 0) return false;
+
+ 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 };
+
+ return count >= maxAddressCount;
+};
+
export default {
getJsonObjectValue,
getSetting,