mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 07:57:28 +00:00
fix: passkey rpid detect
This commit is contained in:
@@ -80,9 +80,11 @@ func BuildWebAuthn(r *http.Request) (*webauthn.WebAuthn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolveOrigins(r *http.Request, settings *system_setting.PasskeySettings) ([]string, error) {
|
func resolveOrigins(r *http.Request, settings *system_setting.PasskeySettings) ([]string, error) {
|
||||||
if len(settings.Origins) > 0 {
|
originsStr := strings.TrimSpace(settings.Origins)
|
||||||
origins := make([]string, 0, len(settings.Origins))
|
if originsStr != "" {
|
||||||
for _, origin := range settings.Origins {
|
originList := strings.Split(originsStr, ",")
|
||||||
|
origins := make([]string, 0, len(originList))
|
||||||
|
for _, origin := range originList {
|
||||||
trimmed := strings.TrimSpace(origin)
|
trimmed := strings.TrimSpace(origin)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
package system_setting
|
package system_setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
"one-api/setting/config"
|
"one-api/setting/config"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PasskeySettings struct {
|
type PasskeySettings struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
RPDisplayName string `json:"rp_display_name"`
|
RPDisplayName string `json:"rp_display_name"`
|
||||||
RPID string `json:"rp_id"`
|
RPID string `json:"rp_id"`
|
||||||
Origins []string `json:"origins"`
|
Origins string `json:"origins"`
|
||||||
AllowInsecureOrigin bool `json:"allow_insecure_origin"`
|
AllowInsecureOrigin bool `json:"allow_insecure_origin"`
|
||||||
UserVerification string `json:"user_verification"`
|
UserVerification string `json:"user_verification"`
|
||||||
AttachmentPreference string `json:"attachment_preference"`
|
AttachmentPreference string `json:"attachment_preference"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultPasskeySettings = PasskeySettings{
|
var defaultPasskeySettings = PasskeySettings{
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
RPDisplayName: common.SystemName,
|
RPDisplayName: common.SystemName,
|
||||||
RPID: "",
|
RPID: "",
|
||||||
Origins: []string{},
|
Origins: "",
|
||||||
AllowInsecureOrigin: false,
|
AllowInsecureOrigin: false,
|
||||||
UserVerification: "preferred",
|
UserVerification: "preferred",
|
||||||
AttachmentPreference: "",
|
AttachmentPreference: "",
|
||||||
@@ -30,5 +32,15 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetPasskeySettings() *PasskeySettings {
|
func GetPasskeySettings() *PasskeySettings {
|
||||||
|
if defaultPasskeySettings.RPID == "" && ServerAddress != "" {
|
||||||
|
// 从ServerAddress提取域名作为RPID
|
||||||
|
// ServerAddress可能是 "https://newapi.pro" 这种格式
|
||||||
|
serverAddr := strings.TrimSpace(ServerAddress)
|
||||||
|
if parsed, err := url.Parse(serverAddr); err == nil && parsed.Host != "" {
|
||||||
|
defaultPasskeySettings.RPID = parsed.Host
|
||||||
|
} else {
|
||||||
|
defaultPasskeySettings.RPID = serverAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
return &defaultPasskeySettings
|
return &defaultPasskeySettings
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ const SystemSetting = () => {
|
|||||||
const [domainList, setDomainList] = useState([]);
|
const [domainList, setDomainList] = useState([]);
|
||||||
const [ipList, setIpList] = useState([]);
|
const [ipList, setIpList] = useState([]);
|
||||||
const [allowedPorts, setAllowedPorts] = useState([]);
|
const [allowedPorts, setAllowedPorts] = useState([]);
|
||||||
const [passkeyOrigins, setPasskeyOrigins] = useState([]);
|
|
||||||
|
|
||||||
const getOptions = async () => {
|
const getOptions = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -188,22 +187,19 @@ const SystemSetting = () => {
|
|||||||
item.value = toBoolean(item.value);
|
item.value = toBoolean(item.value);
|
||||||
break;
|
break;
|
||||||
case 'passkey.origins':
|
case 'passkey.origins':
|
||||||
try {
|
// origins是逗号分隔的字符串,直接使用
|
||||||
const origins = item.value ? JSON.parse(item.value) : [];
|
item.value = item.value || '';
|
||||||
setPasskeyOrigins(Array.isArray(origins) ? origins : []);
|
|
||||||
item.value = Array.isArray(origins) ? origins : [];
|
|
||||||
} catch (e) {
|
|
||||||
setPasskeyOrigins([]);
|
|
||||||
item.value = [];
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'passkey.rp_display_name':
|
case 'passkey.rp_display_name':
|
||||||
case 'passkey.rp_id':
|
case 'passkey.rp_id':
|
||||||
case 'passkey.user_verification':
|
|
||||||
case 'passkey.attachment_preference':
|
case 'passkey.attachment_preference':
|
||||||
// 确保字符串字段不为null/undefined
|
// 确保字符串字段不为null/undefined
|
||||||
item.value = item.value || '';
|
item.value = item.value || '';
|
||||||
break;
|
break;
|
||||||
|
case 'passkey.user_verification':
|
||||||
|
// 确保有默认值
|
||||||
|
item.value = item.value || 'preferred';
|
||||||
|
break;
|
||||||
case 'Price':
|
case 'Price':
|
||||||
case 'MinTopUp':
|
case 'MinTopUp':
|
||||||
item.value = parseFloat(item.value);
|
item.value = parseFloat(item.value);
|
||||||
@@ -611,42 +607,33 @@ const SystemSetting = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitPasskeySettings = async () => {
|
const submitPasskeySettings = async () => {
|
||||||
|
// 使用formApi直接获取当前表单值
|
||||||
|
const formValues = formApiRef.current?.getValues() || {};
|
||||||
|
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
// 只在值有变化时才提交,并确保空值转换为空字符串
|
options.push({
|
||||||
if (originInputs['passkey.rp_display_name'] !== inputs['passkey.rp_display_name']) {
|
key: 'passkey.rp_display_name',
|
||||||
options.push({
|
value: formValues['passkey.rp_display_name'] || inputs['passkey.rp_display_name'] || '',
|
||||||
key: 'passkey.rp_display_name',
|
});
|
||||||
value: inputs['passkey.rp_display_name'] || '',
|
options.push({
|
||||||
});
|
key: 'passkey.rp_id',
|
||||||
}
|
value: formValues['passkey.rp_id'] || inputs['passkey.rp_id'] || '',
|
||||||
if (originInputs['passkey.rp_id'] !== inputs['passkey.rp_id']) {
|
});
|
||||||
options.push({
|
options.push({
|
||||||
key: 'passkey.rp_id',
|
key: 'passkey.user_verification',
|
||||||
value: inputs['passkey.rp_id'] || '',
|
value: formValues['passkey.user_verification'] || inputs['passkey.user_verification'] || 'preferred',
|
||||||
});
|
});
|
||||||
}
|
options.push({
|
||||||
if (originInputs['passkey.user_verification'] !== inputs['passkey.user_verification']) {
|
key: 'passkey.attachment_preference',
|
||||||
options.push({
|
value: formValues['passkey.attachment_preference'] || inputs['passkey.attachment_preference'] || '',
|
||||||
key: 'passkey.user_verification',
|
});
|
||||||
value: inputs['passkey.user_verification'] || 'preferred',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (originInputs['passkey.attachment_preference'] !== inputs['passkey.attachment_preference']) {
|
|
||||||
options.push({
|
|
||||||
key: 'passkey.attachment_preference',
|
|
||||||
value: inputs['passkey.attachment_preference'] || '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Origins总是提交,因为它们可能会被用户清空
|
|
||||||
options.push({
|
options.push({
|
||||||
key: 'passkey.origins',
|
key: 'passkey.origins',
|
||||||
value: JSON.stringify(Array.isArray(passkeyOrigins) ? passkeyOrigins : []),
|
value: formValues['passkey.origins'] || inputs['passkey.origins'] || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (options.length > 0) {
|
await updateOptions(options);
|
||||||
await updateOptions(options);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCheckboxChange = async (optionKey, event) => {
|
const handleCheckboxChange = async (optionKey, event) => {
|
||||||
@@ -1037,7 +1024,7 @@ const SystemSetting = () => {
|
|||||||
>
|
>
|
||||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||||
<Form.Checkbox
|
<Form.Checkbox
|
||||||
field='passkey.enabled'
|
field="['passkey.enabled']"
|
||||||
noLabel
|
noLabel
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleCheckboxChange('passkey.enabled', e)
|
handleCheckboxChange('passkey.enabled', e)
|
||||||
@@ -1052,7 +1039,7 @@ const SystemSetting = () => {
|
|||||||
>
|
>
|
||||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
field='passkey.rp_display_name'
|
field="['passkey.rp_display_name']"
|
||||||
label={t('服务显示名称')}
|
label={t('服务显示名称')}
|
||||||
placeholder={t('默认使用系统名称')}
|
placeholder={t('默认使用系统名称')}
|
||||||
extraText={t('用户注册时看到的网站名称,比如\'我的网站\'')}
|
extraText={t('用户注册时看到的网站名称,比如\'我的网站\'')}
|
||||||
@@ -1060,7 +1047,7 @@ const SystemSetting = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||||
<Form.Input
|
<Form.Input
|
||||||
field='passkey.rp_id'
|
field="['passkey.rp_id']"
|
||||||
label={t('网站域名标识')}
|
label={t('网站域名标识')}
|
||||||
placeholder={t('例如:example.com')}
|
placeholder={t('例如:example.com')}
|
||||||
extraText={t('留空自动使用当前域名')}
|
extraText={t('留空自动使用当前域名')}
|
||||||
@@ -1073,7 +1060,7 @@ const SystemSetting = () => {
|
|||||||
>
|
>
|
||||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||||
<Form.Select
|
<Form.Select
|
||||||
field='passkey.user_verification'
|
field="['passkey.user_verification']"
|
||||||
label={t('安全验证级别')}
|
label={t('安全验证级别')}
|
||||||
placeholder={t('是否要求指纹/面容等生物识别')}
|
placeholder={t('是否要求指纹/面容等生物识别')}
|
||||||
optionList={[
|
optionList={[
|
||||||
@@ -1086,7 +1073,7 @@ const SystemSetting = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
<Col xs={24} sm={24} md={12} lg={12} xl={12}>
|
||||||
<Form.Select
|
<Form.Select
|
||||||
field='passkey.attachment_preference'
|
field="['passkey.attachment_preference']"
|
||||||
label={t('设备类型偏好')}
|
label={t('设备类型偏好')}
|
||||||
placeholder={t('选择支持的认证设备类型')}
|
placeholder={t('选择支持的认证设备类型')}
|
||||||
optionList={[
|
optionList={[
|
||||||
@@ -1104,7 +1091,7 @@ const SystemSetting = () => {
|
|||||||
>
|
>
|
||||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||||
<Form.Checkbox
|
<Form.Checkbox
|
||||||
field='passkey.allow_insecure_origin'
|
field="['passkey.allow_insecure_origin']"
|
||||||
noLabel
|
noLabel
|
||||||
extraText={t('仅用于开发环境,生产环境应使用 HTTPS')}
|
extraText={t('仅用于开发环境,生产环境应使用 HTTPS')}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@@ -1120,21 +1107,11 @@ const SystemSetting = () => {
|
|||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 16 }}
|
||||||
>
|
>
|
||||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||||
<Text strong>{t('允许的 Origins')}</Text>
|
<Form.Input
|
||||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
field="['passkey.origins']"
|
||||||
{t('留空将自动使用服务器地址,多个 Origin 用于支持多域名部署')}
|
label={t('允许的 Origins')}
|
||||||
</Text>
|
placeholder={t('填写带https的域名,逗号分隔')}
|
||||||
<TagInput
|
extraText={t('空的话则不限制 Origin,多个 Origin 用逗号分隔')}
|
||||||
value={passkeyOrigins}
|
|
||||||
onChange={(value) => {
|
|
||||||
setPasskeyOrigins(value);
|
|
||||||
setInputs(prev => ({
|
|
||||||
...prev,
|
|
||||||
'passkey.origins': value
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
placeholder={t('输入 Origin 后回车,如:https://example.com')}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
Reference in New Issue
Block a user