Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
d826be82
authored
Sep 30, 2025
by
Calcium-Ion
Committed by
GitHub
Sep 30, 2025
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1934 from seefs001/fix/passkey
fix: passkey rpid detect
parents
92f81c63
75ee6321
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
63 additions
and
72 deletions
+63
-72
service/passkey/service.go
+5
-3
setting/system_setting/passkey.go
+20
-8
web/src/components/settings/SystemSetting.jsx
+38
-61
No files found.
service/passkey/service.go
View file @
d826be82
...
@@ -80,9 +80,11 @@ func BuildWebAuthn(r *http.Request) (*webauthn.WebAuthn, error) {
...
@@ -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
...
...
setting/system_setting/passkey.go
View file @
d826be82
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() {
...
@@ -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
}
}
web/src/components/settings/SystemSetting.jsx
View file @
d826be82
...
@@ -122,7 +122,6 @@ const SystemSetting = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment