feat: add support for client-side discoverable WebAuthn login (#5722)

* Add support for client-side discoverable in begin login

Use `(*webauthn.WebAuthn).BeginDiscoverableLogin()` to handle client-side discoverable login.

* Upgrade github.com/go-webauthn/webauthn to v0.10.0

Upgrade [go-webauthn/webauthn](github.com/go-webauthn/webauthn) library to latest.

The convenient finish login function (as FinishDiscoverableLogin) for discoverable functions has been added in the v0.9.0. [^1]

---

[^1]: https://github.com/go-webauthn/webauthn/releases/tag/v0.9.0

* Add support for client-side discoverable in validating login

Use `(*webauthn.WebAuthn).FinishDiscoverableLogin()` to handle client-side discoverable login.

> **NOTE**:
- The first param `rawID` in this callback function is unnecessary to check, it's handled by the third-party webauthn library later.
- `userHandle` param is equal to the ID returned by (User).WebAuthnID() function.
This commit is contained in:
Feng.YJ
2023-12-24 15:21:17 +08:00
committed by GitHub
parent ab216ed170
commit 3eca38e599
3 changed files with 53 additions and 36 deletions

View File

@ -2,6 +2,7 @@ package handles
import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
@ -13,6 +14,7 @@ import (
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
)
@ -22,28 +24,30 @@ func BeginAuthnLogin(c *gin.Context) {
common.ErrorStrResp(c, "WebAuthn is not enabled", 403)
return
}
username := c.Query("username")
if username == "" {
common.ErrorStrResp(c, "empty or no username provided", 400)
return
}
user, err := db.GetUserByName(username)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
authnInstance, err := authn.NewAuthnInstance(c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
options, sessionData, err := authnInstance.BeginLogin(user)
var (
options *protocol.CredentialAssertion
sessionData *webauthn.SessionData
)
if username := c.Query("username"); username != "" {
var user *model.User
user, err = db.GetUserByName(username)
if err == nil {
options, sessionData, err = authnInstance.BeginLogin(user)
}
} else { // client-side discoverable login
options, sessionData, err = authnInstance.BeginDiscoverableLogin()
}
if err != nil {
common.ErrorResp(c, err, 400)
return
}
val, err := json.Marshal(sessionData)
if err != nil {
common.ErrorResp(c, err, 400)
@ -61,20 +65,13 @@ func FinishAuthnLogin(c *gin.Context) {
common.ErrorStrResp(c, "WebAuthn is not enabled", 403)
return
}
username := c.Query("username")
user, err := db.GetUserByName(username)
authnInstance, err := authn.NewAuthnInstance(c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
sessionDataString := c.GetHeader("session")
authnInstance, err := authn.NewAuthnInstance(c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
sessionDataBytes, err := base64.StdEncoding.DecodeString(sessionDataString)
if err != nil {
common.ErrorResp(c, err, 400)
@ -87,8 +84,28 @@ func FinishAuthnLogin(c *gin.Context) {
return
}
_, err = authnInstance.FinishLogin(user, sessionData, c.Request)
var user *model.User
if username := c.Query("username"); username != "" {
user, err = db.GetUserByName(username)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
_, err = authnInstance.FinishLogin(user, sessionData, c.Request)
} else { // client-side discoverable login
_, err = authnInstance.FinishDiscoverableLogin(func(_, userHandle []byte) (webauthn.User, error) {
// first param `rawID` in this callback function is equal to ID in webauthn.Credential,
// but it's unnnecessary to check it.
// userHandle param is equal to (User).WebAuthnID().
userID := uint(binary.LittleEndian.Uint64(userHandle))
user, err = db.GetUserById(userID)
if err != nil {
return nil, err
}
return user, nil
}, sessionData, c.Request)
}
if err != nil {
common.ErrorResp(c, err, 400)
return