feat(auth): 添加用户注册开关功能
- 在系统设置中新增 allowRegister 配置项控制用户注册 - 实现注册接口的开关检查机制 - 登录页面根据注册开关动态显示或隐藏注册选项 - 首次 Linux.do 登录创建用户时也受注册开关限制 - 更新系统配置文档说明新的认证配置项
This commit is contained in:
@@ -22,6 +22,12 @@
|
|||||||
"defaultTextModel": "gpt-5.5",
|
"defaultTextModel": "gpt-5.5",
|
||||||
"systemPrompt": "",
|
"systemPrompt": "",
|
||||||
"allowCustomChannel": true
|
"allowCustomChannel": true
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"allowRegister": true,
|
||||||
|
"linuxDo": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -29,6 +35,7 @@
|
|||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `modelChannel` | object | 模型渠道公开配置组 |
|
| `modelChannel` | object | 模型渠道公开配置组 |
|
||||||
|
| `auth` | object | 认证相关公开配置 |
|
||||||
|
|
||||||
`modelChannel` 字段:
|
`modelChannel` 字段:
|
||||||
|
|
||||||
@@ -56,6 +63,13 @@
|
|||||||
| 云端渠道 | 使用后端 `/api/v1/*` 代理接口,请求会按模型名匹配 `private.value.channels` 中的可用渠道 |
|
| 云端渠道 | 使用后端 `/api/v1/*` 代理接口,请求会按模型名匹配 `private.value.channels` 中的可用渠道 |
|
||||||
| 本地直连 | 默认可选;`allowCustomChannel` 关闭后不可选,用户在浏览器本地配置 `baseUrl`、`apiKey` 和模型列表后直接请求模型接口 |
|
| 本地直连 | 默认可选;`allowCustomChannel` 关闭后不可选,用户在浏览器本地配置 `baseUrl`、`apiKey` 和模型列表后直接请求模型接口 |
|
||||||
|
|
||||||
|
`auth` 字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `allowRegister` | boolean | 是否允许用户注册,默认允许;关闭后注册入口隐藏,注册接口拒绝新用户创建 |
|
||||||
|
| `linuxDo.enabled` | boolean | 是否开启 Linux.do 登录 |
|
||||||
|
|
||||||
## private.value
|
## private.value
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
+2
-1
@@ -46,7 +46,8 @@ type PublicSetting struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PublicAuthSetting struct {
|
type PublicAuthSetting struct {
|
||||||
LinuxDo PublicLinuxDoAuthSetting `json:"linuxDo"`
|
AllowRegister *bool `json:"allowRegister"`
|
||||||
|
LinuxDo PublicLinuxDoAuthSetting `json:"linuxDo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicLinuxDoAuthSetting struct {
|
type PublicLinuxDoAuthSetting struct {
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ func EnsureDefaultAdmin() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Register(username string, password string) (model.AuthSession, error) {
|
func Register(username string, password string) (model.AuthSession, error) {
|
||||||
|
settings, err := repository.GetSettings()
|
||||||
|
if err != nil {
|
||||||
|
return model.AuthSession{}, err
|
||||||
|
}
|
||||||
|
normalizedSettings := normalizeSettings(settings)
|
||||||
|
if normalizedSettings.Public.Auth.AllowRegister != nil && !*normalizedSettings.Public.Auth.AllowRegister {
|
||||||
|
return model.AuthSession{}, safeMessageError{message: "当前未开放注册"}
|
||||||
|
}
|
||||||
username = strings.TrimSpace(username)
|
username = strings.TrimSpace(username)
|
||||||
if strings.ContainsAny(username, " \t\r\n") {
|
if strings.ContainsAny(username, " \t\r\n") {
|
||||||
return model.AuthSession{}, safeMessageError{message: "用户名不能包含空格"}
|
return model.AuthSession{}, safeMessageError{message: "用户名不能包含空格"}
|
||||||
@@ -163,6 +171,9 @@ func LoginWithLinuxDo(r *http.Request, code string, state string) (model.AuthSes
|
|||||||
return model.AuthSession{}, redirect, err
|
return model.AuthSession{}, redirect, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if settings.Public.Auth.AllowRegister != nil && !*settings.Public.Auth.AllowRegister {
|
||||||
|
return model.AuthSession{}, redirect, safeMessageError{message: "当前未开放注册"}
|
||||||
|
}
|
||||||
user = model.User{
|
user = model.User{
|
||||||
ID: newID("user"),
|
ID: newID("user"),
|
||||||
Username: linuxDoUsername(profile.Username, linuxDoID),
|
Username: linuxDoUsername(profile.Username, linuxDoID),
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ func normalizePublicSetting(setting model.PublicSetting) model.PublicSetting {
|
|||||||
enabled := true
|
enabled := true
|
||||||
setting.ModelChannel.AllowCustomChannel = &enabled
|
setting.ModelChannel.AllowCustomChannel = &enabled
|
||||||
}
|
}
|
||||||
|
if setting.Auth.AllowRegister == nil {
|
||||||
|
enabled := true
|
||||||
|
setting.Auth.AllowRegister = &enabled
|
||||||
|
}
|
||||||
return setting
|
return setting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const emptySettings: AdminSettings = {
|
|||||||
systemPrompt: "",
|
systemPrompt: "",
|
||||||
allowCustomChannel: true,
|
allowCustomChannel: true,
|
||||||
},
|
},
|
||||||
auth: { linuxDo: { enabled: false } },
|
auth: { allowRegister: true, linuxDo: { enabled: false } },
|
||||||
},
|
},
|
||||||
private: { channels: [], promptSync: { enabled: true, cron: "*/5 * * * *" }, auth: { linuxDo: { clientId: "", clientSecret: "" } } },
|
private: { channels: [], promptSync: { enabled: true, cron: "*/5 * * * *" }, auth: { linuxDo: { clientId: "", clientSecret: "" } } },
|
||||||
};
|
};
|
||||||
@@ -371,6 +371,11 @@ export default function AdminSettingsPage() {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item name={["public", "auth", "allowRegister"]} label="是否允许用户注册" extra="关闭后隐藏注册入口,注册接口也会拒绝新用户创建" valuePropName="checked">
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Typography.Title level={5}>模型算力点</Typography.Title>
|
<Typography.Title level={5}>模型算力点</Typography.Title>
|
||||||
<Table
|
<Table
|
||||||
@@ -689,6 +694,7 @@ function normalizePublicSetting(setting: Partial<AdminSettings["public"]> = {}):
|
|||||||
modelCosts: normalizeModelCosts(setting.modelChannel?.modelCosts || []),
|
modelCosts: normalizeModelCosts(setting.modelChannel?.modelCosts || []),
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
|
allowRegister: setting.auth?.allowRegister !== false,
|
||||||
linuxDo: {
|
linuxDo: {
|
||||||
enabled: setting.auth?.linuxDo?.enabled === true,
|
enabled: setting.auth?.linuxDo?.enabled === true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ function LoginContent() {
|
|||||||
const setSession = useUserStore((state) => state.setSession);
|
const setSession = useUserStore((state) => state.setSession);
|
||||||
const isLoading = useUserStore((state) => state.isLoading);
|
const isLoading = useUserStore((state) => state.isLoading);
|
||||||
const linuxDoEnabled = useConfigStore((state) => state.publicSettings?.auth?.linuxDo?.enabled === true);
|
const linuxDoEnabled = useConfigStore((state) => state.publicSettings?.auth?.linuxDo?.enabled === true);
|
||||||
|
const allowRegister = useConfigStore((state) => state.publicSettings?.auth?.allowRegister !== false);
|
||||||
const [mode, setMode] = useState<"login" | "register">("login");
|
const [mode, setMode] = useState<"login" | "register">("login");
|
||||||
const redirect = searchParams.get("redirect") || "/";
|
const redirect = searchParams.get("redirect") || "/";
|
||||||
|
|
||||||
@@ -48,8 +49,16 @@ function LoginContent() {
|
|||||||
});
|
});
|
||||||
}, [message, redirect, router, searchParams, setSession]);
|
}, [message, redirect, router, searchParams, setSession]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!allowRegister && mode === "register") setMode("login");
|
||||||
|
}, [allowRegister, mode]);
|
||||||
|
|
||||||
const submit = async (values: LoginFormValues) => {
|
const submit = async (values: LoginFormValues) => {
|
||||||
try {
|
try {
|
||||||
|
if (mode === "register" && !allowRegister) {
|
||||||
|
message.error("当前未开放注册");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (mode === "register" && values.password !== values.confirmPassword) {
|
if (mode === "register" && values.password !== values.confirmPassword) {
|
||||||
message.error("两次输入的密码不一致");
|
message.error("两次输入的密码不一致");
|
||||||
return;
|
return;
|
||||||
@@ -87,10 +96,7 @@ function LoginContent() {
|
|||||||
block
|
block
|
||||||
value={mode}
|
value={mode}
|
||||||
onChange={(value) => setMode(value as "login" | "register")}
|
onChange={(value) => setMode(value as "login" | "register")}
|
||||||
options={[
|
options={allowRegister ? [{ label: "登录", value: "login" }, { label: "注册", value: "register" }] : [{ label: "登录", value: "login" }]}
|
||||||
{ label: "登录", value: "login" },
|
|
||||||
{ label: "注册", value: "register" },
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="username" label={<span className="font-medium text-stone-800 dark:text-stone-200">用户名</span>} rules={[{ required: true, message: "请输入用户名" }]}>
|
<Form.Item name="username" label={<span className="font-medium text-stone-800 dark:text-stone-200">用户名</span>} rules={[{ required: true, message: "请输入用户名" }]}>
|
||||||
@@ -104,7 +110,7 @@ function LoginContent() {
|
|||||||
<Input.Password prefix={<LockOutlined />} autoComplete="new-password" />
|
<Input.Password prefix={<LockOutlined />} autoComplete="new-password" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Space direction="vertical" size={12} style={{ width: "100%" }}>
|
<Space orientation="vertical" size={12} style={{ width: "100%" }}>
|
||||||
<Button block type="primary" htmlType="submit" loading={isLoading}>
|
<Button block type="primary" htmlType="submit" loading={isLoading}>
|
||||||
{mode === "register" ? "注册" : "登录"}
|
{mode === "register" ? "注册" : "登录"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ export type AdminModelCost = {
|
|||||||
export type AdminPublicSettings = {
|
export type AdminPublicSettings = {
|
||||||
modelChannel: AdminPublicModelChannelSettings;
|
modelChannel: AdminPublicModelChannelSettings;
|
||||||
auth: {
|
auth: {
|
||||||
|
allowRegister: boolean;
|
||||||
linuxDo: {
|
linuxDo: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user