fix: passkey rpid detect

This commit is contained in:
Seefs
2025-09-30 15:52:31 +08:00
parent 7533ffc3ee
commit fcc6172b43
3 changed files with 63 additions and 72 deletions

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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>