Skip to content

Commit a8f6a52

Browse files
committed
feat: 支持禁用注册
1 parent e489434 commit a8f6a52

9 files changed

Lines changed: 335 additions & 13 deletions

File tree

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,7 @@ OCR_DEFAULT_PROVIDER=zhipu
102102
ZHIPU_API_KEY=your-zhipu-api-key
103103
# 智谱 OCR API Base URL
104104
ZHIPU_OCR_BASE_URL=https://open.bigmodel.cn/api
105+
106+
# ===== 用户注册配置 =====
107+
# 是否允许用户注册
108+
ALLOW_REGISTRATION=true

REGISTRATION_CONTROL.md

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# 用户注册控制功能
2+
3+
## 概述
4+
5+
通过环境变量 `ALLOW_REGISTRATION` 控制系统是否允许新用户注册,支持服务端校验和前端 UI 控制。
6+
7+
## 配置
8+
9+
### 环境变量
10+
11+
`.env` 文件中添加:
12+
13+
```bash
14+
# 是否允许用户注册(默认为 true)
15+
ALLOW_REGISTRATION=true
16+
```
17+
18+
- `true` - 允许用户注册(默认值)
19+
- `false` - 禁止用户注册
20+
21+
## 实现细节
22+
23+
### 1. 后端实现
24+
25+
#### 配置层 (`apps/server/src/config/config.ts`)
26+
27+
```typescript
28+
auth: {
29+
allowRegistration: process.env.ALLOW_REGISTRATION !== 'false', // 默认允许注册
30+
}
31+
```
32+
33+
#### 系统配置接口 (`apps/server/src/controllers/v1/system.controller.ts`)
34+
35+
新增公共接口返回系统配置:
36+
37+
```typescript
38+
@Get('/config')
39+
async getConfig() {
40+
return ResponseUtility.success({
41+
allowRegistration: config.auth.allowRegistration,
42+
});
43+
}
44+
```
45+
46+
- 路径:`GET /api/v1/system/config`
47+
- 权限:公开接口,无需认证
48+
- 返回:`{ allowRegistration: boolean }`
49+
50+
#### 注册接口校验 (`apps/server/src/controllers/v1/auth.controller.ts`)
51+
52+
在注册接口中添加校验:
53+
54+
```typescript
55+
@Post('/register')
56+
async register(@Body() userData: RegisterDto) {
57+
// 检查是否允许注册
58+
if (!config.auth.allowRegistration) {
59+
return ResponseUtility.error(
60+
ErrorCode.OPERATION_NOT_ALLOWED,
61+
'Registration is currently disabled'
62+
);
63+
}
64+
// ... 其他注册逻辑
65+
}
66+
```
67+
68+
#### 错误码 (`apps/server/src/constants/error-codes.ts`)
69+
70+
新增错误码:
71+
72+
```typescript
73+
OPERATION_NOT_ALLOWED: 6, // 操作不被允许
74+
```
75+
76+
### 2. 前端实现
77+
78+
#### API 层 (`apps/web/src/api/system.ts`)
79+
80+
新增获取系统配置的 API:
81+
82+
```typescript
83+
export const getSystemConfig = () => {
84+
return request.get<unknown, { code: number; msg: string; data: { allowRegistration: boolean } }>(
85+
'/api/v1/system/config'
86+
);
87+
};
88+
```
89+
90+
#### UI 层 (`apps/web/src/pages/auth/auth.tsx`)
91+
92+
- 页面加载时获取系统配置
93+
- 根据 `allowRegistration` 控制注册入口显示/隐藏
94+
- 如果注册被禁用且用户在注册页面,自动跳转到登录页面
95+
96+
```typescript
97+
// 获取系统配置
98+
useEffect(() => {
99+
const fetchConfig = async () => {
100+
const response = await getSystemConfig();
101+
if (response.code === 0) {
102+
setAllowRegistration(response.data.allowRegistration);
103+
// 如果注册被禁用且在注册页面,跳转到登录
104+
if (!response.data.allowRegistration && !isLogin) {
105+
navigate('/auth?mode=login', { replace: true });
106+
}
107+
}
108+
};
109+
fetchConfig();
110+
}, [isLogin, navigate]);
111+
112+
// 条件渲染注册入口
113+
{allowRegistration && (
114+
<div className="mt-6 text-center">
115+
<button onClick={...}>
116+
{isLogin ? "Don't have an account? Sign up" : 'Already have an account? Sign in'}
117+
</button>
118+
</div>
119+
)}
120+
```
121+
122+
## 使用场景
123+
124+
### 场景 1: 完全开放注册(默认)
125+
126+
```bash
127+
# .env
128+
ALLOW_REGISTRATION=true
129+
# 或者不设置(默认为 true)
130+
```
131+
132+
- 用户可以正常访问注册页面
133+
- 注册接口正常工作
134+
- 登录页面显示"注册"入口
135+
136+
### 场景 2: 禁止注册
137+
138+
```bash
139+
# .env
140+
ALLOW_REGISTRATION=false
141+
```
142+
143+
- 登录页面隐藏"注册"入口
144+
- 直接访问注册页面会自动跳转到登录页面
145+
- 调用注册接口返回错误:`{ code: 6, msg: "Registration is currently disabled" }`
146+
147+
### 场景 3: 内部部署/私有化场景
148+
149+
适用于企业内部部署,只允许管理员创建账户:
150+
151+
1. 设置 `ALLOW_REGISTRATION=false`
152+
2. 管理员通过数据库直接创建用户账户
153+
3. 普通用户只能登录,无法自行注册
154+
155+
## 安全性
156+
157+
### 多层防护
158+
159+
1. **前端 UI 控制**:隐藏注册入口,提升用户体验
160+
2. **前端路由拦截**:访问注册页面自动跳转到登录页
161+
3. **后端接口校验**:即使绕过前端,后端也会拒绝注册请求
162+
163+
### 防止绕过
164+
165+
即使用户通过以下方式尝试注册,也会被后端拦截:
166+
167+
- 直接访问 `/auth?mode=register`
168+
- 使用 API 工具直接调用 `/api/v1/auth/register`
169+
- 修改前端代码
170+
171+
所有这些尝试都会收到 `OPERATION_NOT_ALLOWED` 错误。
172+
173+
## 测试
174+
175+
### 测试允许注册
176+
177+
```bash
178+
# 1. 设置环境变量
179+
echo "ALLOW_REGISTRATION=true" >> .env
180+
181+
# 2. 启动服务
182+
pnpm dev
183+
184+
# 3. 验证
185+
# - 访问 http://localhost:5173/auth
186+
# - 应该能看到"注册"入口
187+
# - 可以正常注册新用户
188+
```
189+
190+
### 测试禁止注册
191+
192+
```bash
193+
# 1. 设置环境变量
194+
echo "ALLOW_REGISTRATION=false" >> .env
195+
196+
# 2. 启动服务
197+
pnpm dev
198+
199+
# 3. 验证
200+
# - 访问 http://localhost:5173/auth
201+
# - 看不到"注册"入口
202+
# - 访问 http://localhost:5173/auth?mode=register 会自动跳转到登录页
203+
# - 直接调用注册 API 会返回错误
204+
```
205+
206+
### API 测试
207+
208+
```bash
209+
# 1. 获取系统配置
210+
curl http://localhost:3000/api/v1/system/config
211+
212+
# 预期响应(允许注册):
213+
# {
214+
# "code": 0,
215+
# "msg": "Success",
216+
# "data": {
217+
# "allowRegistration": true
218+
# }
219+
# }
220+
221+
# 2. 尝试注册(当 ALLOW_REGISTRATION=false 时)
222+
curl -X POST http://localhost:3000/api/v1/auth/register \
223+
-H "Content-Type: application/json" \
224+
-d '{"email":"test@example.com","password":"123456"}'
225+
226+
# 预期响应(禁止注册):
227+
# {
228+
# "code": 6,
229+
# "msg": "Registration is currently disabled",
230+
# "data": null
231+
# }
232+
```
233+
234+
## 文件变更清单
235+
236+
### 后端
237+
238+
-`.env.example` - 添加 `ALLOW_REGISTRATION` 环境变量说明
239+
-`apps/server/src/config/config.ts` - 添加 `auth.allowRegistration` 配置
240+
-`apps/server/src/constants/error-codes.ts` - 添加 `OPERATION_NOT_ALLOWED` 错误码
241+
-`apps/server/src/controllers/v1/system.controller.ts` - 添加 `/config` 接口
242+
-`apps/server/src/controllers/v1/auth.controller.ts` - 添加注册校验
243+
244+
### 前端
245+
246+
-`apps/web/src/api/system.ts` - 添加 `getSystemConfig` API
247+
-`apps/web/src/pages/auth/auth.tsx` - 添加配置获取和 UI 控制
248+
249+
## 未来扩展
250+
251+
可以基于此功能继续扩展:
252+
253+
1. **邀请码注册**:即使禁止公开注册,也可以通过邀请码注册
254+
2. **注册审核**:允许注册但需要管理员审核
255+
3. **注册限流**:限制每天/每小时的注册数量
256+
4. **域名白名单**:只允许特定邮箱域名注册(如 @company.com)
257+
5. **更多配置项**:通过 `/api/v1/system/config` 返回更多公开配置

apps/client/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/main/index.ts","./src/preload/index.ts"],"errors":true,"version":"5.9.3"}
1+
{"root":["./src/main/index.ts","./src/preload/index.ts"],"version":"5.9.3"}

apps/server/src/config/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export interface Config {
133133
enabled: boolean;
134134
token: string;
135135
};
136+
auth: {
137+
allowRegistration: boolean; // 是否允许用户注册
138+
};
136139
env: string;
137140
}
138141

@@ -278,5 +281,8 @@ export const config: Config = {
278281
enabled: process.env.BA_AUTH_ENABLED === 'true',
279282
token: process.env.BA_AUTH_TOKEN || '',
280283
},
284+
auth: {
285+
allowRegistration: process.env.ALLOW_REGISTRATION !== 'false', // 默认允许注册
286+
},
281287
env: process.env.NODE_ENV || 'development',
282288
};

apps/server/src/constants/error-codes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ErrorCode = {
66
NOT_FOUND: 3,
77
UNAUTHORIZED: 4,
88
FORBIDDEN: 5,
9+
OPERATION_NOT_ALLOWED: 6,
910

1011
// 用户相关错误: 1000-1999
1112
USER_NOT_FOUND: 1000,
@@ -36,6 +37,7 @@ export const ErrorMessage = {
3637
[ErrorCode.NOT_FOUND]: '资源不存在',
3738
[ErrorCode.UNAUTHORIZED]: '未授权',
3839
[ErrorCode.FORBIDDEN]: '禁止访问',
40+
[ErrorCode.OPERATION_NOT_ALLOWED]: '操作不被允许',
3941
[ErrorCode.USER_NOT_FOUND]: '用户不存在',
4042
[ErrorCode.USER_ALREADY_EXISTS]: '用户已存在',
4143
[ErrorCode.PASSWORD_ERROR]: '密码错误',

apps/server/src/controllers/v1/auth.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ export class AuthV1Controller {
1919
@Post('/register')
2020
async register(@Body() userData: RegisterDto) {
2121
try {
22+
// Check if registration is allowed
23+
if (!config.auth.allowRegistration) {
24+
return ResponseUtility.error(
25+
ErrorCode.OPERATION_NOT_ALLOWED,
26+
'Registration is currently disabled'
27+
);
28+
}
29+
2230
if (!userData.email || !userData.password) {
2331
return ResponseUtility.error(ErrorCode.PARAMS_ERROR, 'Email and password are required');
2432
}

apps/server/src/controllers/v1/system.controller.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { JsonController, Get, CurrentUser } from 'routing-controllers';
22
import { Service } from 'typedi';
33

4+
import { config } from '../../config/config.js';
45
import { ErrorCode } from '../../constants/error-codes.js';
56
import { GitHubReleaseService } from '../../services/github-release.service.js';
67
import { logger } from '../../utils/logger.js';
@@ -39,4 +40,15 @@ export class SystemController {
3940
);
4041
}
4142
}
43+
44+
/**
45+
* Get public system configuration
46+
* Public endpoint - no authentication required
47+
*/
48+
@Get('/config')
49+
async getConfig() {
50+
return ResponseUtility.success({
51+
allowRegistration: config.auth.allowRegistration,
52+
});
53+
}
4254
}

apps/web/src/api/system.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ export const getAppVersions = () => {
1919
'/api/v1/system/app-versions'
2020
);
2121
};
22+
23+
/**
24+
* Get public system configuration
25+
* Public endpoint - no authentication required
26+
*/
27+
export const getSystemConfig = () => {
28+
return request.get<unknown, { code: number; msg: string; data: { allowRegistration: boolean } }>(
29+
'/api/v1/system/config'
30+
);
31+
};

0 commit comments

Comments
 (0)