mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-09 06:27:48 +08:00
feat: add back support for non-discoverable credential WebAuthn login and display WebAuthn ID again (#3998)
This commit is contained in:
@@ -17,6 +17,7 @@ package controllers
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
@@ -47,6 +48,13 @@ func (c *ApiController) WebAuthnSignupBegin() {
|
||||
|
||||
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
|
||||
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
|
||||
credCreationOpts.AuthenticatorSelection.ResidentKey = "preferred"
|
||||
credCreationOpts.Attestation = "none"
|
||||
|
||||
ext := map[string]interface{}{
|
||||
"credProps": true,
|
||||
}
|
||||
credCreationOpts.Extensions = ext
|
||||
}
|
||||
options, sessionData, err := webauthnObj.BeginRegistration(
|
||||
user,
|
||||
@@ -118,7 +126,34 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
||||
return
|
||||
}
|
||||
|
||||
options, sessionData, err := webauthnObj.BeginDiscoverableLogin()
|
||||
userOwner := c.Input().Get("owner")
|
||||
userName := c.Input().Get("name")
|
||||
|
||||
var options *protocol.CredentialAssertion
|
||||
var sessionData *webauthn.SessionData
|
||||
|
||||
if userName == "" {
|
||||
options, sessionData, err = webauthnObj.BeginDiscoverableLogin()
|
||||
} else {
|
||||
var user *object.User
|
||||
user, err = object.GetUserByFields(userOwner, userName)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
|
||||
return
|
||||
}
|
||||
if len(user.WebauthnCredentials) == 0 {
|
||||
c.ResponseError(c.T("webauthn:Found no credentials for this user"))
|
||||
return
|
||||
}
|
||||
|
||||
options, sessionData, err = webauthnObj.BeginLogin(user)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -153,15 +188,27 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
||||
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
|
||||
|
||||
var user *object.User
|
||||
handler := func(rawID, userHandle []byte) (webauthn.User, error) {
|
||||
user, err = object.GetUserByWebauthID(base64.StdEncoding.EncodeToString(rawID))
|
||||
if sessionData.UserID != nil {
|
||||
userId := string(sessionData.UserID)
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
return user, nil
|
||||
|
||||
_, err = webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
|
||||
} else {
|
||||
handler := func(rawID, userHandle []byte) (webauthn.User, error) {
|
||||
user, err = object.GetUserByWebauthID(base64.StdEncoding.EncodeToString(rawID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
_, err = webauthnObj.FinishDiscoverableLogin(handler, sessionData, c.Ctx.Request)
|
||||
}
|
||||
|
||||
_, err = webauthnObj.FinishDiscoverableLogin(handler, sessionData, c.Ctx.Request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -637,9 +637,6 @@ class LoginPage extends React.Component {
|
||||
)
|
||||
;
|
||||
} else if (signinItem.name === "Username") {
|
||||
if (this.state.loginMethod === "webAuthn") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div key={resultItemKey}>
|
||||
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
|
||||
@@ -649,7 +646,7 @@ class LoginPage extends React.Component {
|
||||
label={signinItem.label ? signinItem.label : null}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
required: this.state.loginMethod !== "webAuthn",
|
||||
message: () => {
|
||||
switch (this.state.loginMethod) {
|
||||
case "verificationCodeEmail":
|
||||
@@ -1093,7 +1090,8 @@ class LoginPage extends React.Component {
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
this.populateOauthValues(values);
|
||||
const application = this.getApplicationObj();
|
||||
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}`, {
|
||||
const usernameParam = `&name=${encodeURIComponent(username)}`;
|
||||
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}${username ? usernameParam : ""}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
})
|
||||
@@ -1105,6 +1103,12 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
credentialRequestOptions.publicKey.challenge = UserWebauthnBackend.webAuthnBufferDecode(credentialRequestOptions.publicKey.challenge);
|
||||
|
||||
if (username) {
|
||||
credentialRequestOptions.publicKey.allowCredentials.forEach(function(listItem) {
|
||||
listItem.id = UserWebauthnBackend.webAuthnBufferDecode(listItem.id);
|
||||
});
|
||||
}
|
||||
|
||||
return navigator.credentials.get({
|
||||
publicKey: credentialRequestOptions.publicKey,
|
||||
});
|
||||
|
@@ -31,6 +31,7 @@ export function registerWebauthnCredential() {
|
||||
credentialCreationOptions.publicKey.excludeCredentials[i].id = webAuthnBufferDecode(credentialCreationOptions.publicKey.excludeCredentials[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
return navigator.credentials.create({
|
||||
publicKey: credentialCreationOptions.publicKey,
|
||||
});
|
||||
|
@@ -42,8 +42,9 @@ class WebAuthnCredentialTable extends React.Component {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "ID",
|
||||
key: "ID",
|
||||
dataIndex: "id",
|
||||
key: "id",
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
@@ -60,7 +61,7 @@ class WebAuthnCredentialTable extends React.Component {
|
||||
];
|
||||
|
||||
return (
|
||||
<Table rowKey={"ID"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
|
||||
<Table rowKey={"id"} columns={columns} dataSource={this.props.table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("user:WebAuthn credentials")}
|
||||
|
Reference in New Issue
Block a user