alist/server/handles/webauthn.go

235 lines
5.6 KiB
Go
Raw Normal View History

package handles
import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/alist-org/alist/v3/internal/authn"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"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"
)
func BeginAuthnLogin(c *gin.Context) {
enabled := setting.GetBool(conf.WebauthnLoginEnabled)
if !enabled {
common.ErrorStrResp(c, "WebAuthn is not enabled", 403)
return
}
authnInstance, err := authn.NewAuthnInstance(c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
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)
return
}
common.SuccessResp(c, gin.H{
"options": options,
"session": val,
})
}
func FinishAuthnLogin(c *gin.Context) {
enabled := setting.GetBool(conf.WebauthnLoginEnabled)
if !enabled {
common.ErrorStrResp(c, "WebAuthn is not enabled", 403)
return
}
authnInstance, err := authn.NewAuthnInstance(c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
sessionDataString := c.GetHeader("session")
sessionDataBytes, err := base64.StdEncoding.DecodeString(sessionDataString)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
var sessionData webauthn.SessionData
if err := json.Unmarshal(sessionDataBytes, &sessionData); err != nil {
common.ErrorResp(c, err, 400)
return
}
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
}
token, err := common.GenerateToken(user)
if err != nil {
common.ErrorResp(c, err, 400, true)
return
}
common.SuccessResp(c, gin.H{"token": token})
}
func BeginAuthnRegistration(c *gin.Context) {
enabled := setting.GetBool(conf.WebauthnLoginEnabled)
if !enabled {
common.ErrorStrResp(c, "WebAuthn is not enabled", 403)
return
}
user := c.MustGet("user").(*model.User)
authnInstance, err := authn.NewAuthnInstance(c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
}
options, sessionData, err := authnInstance.BeginRegistration(user)
if err != nil {
common.ErrorResp(c, err, 400)
}
val, err := json.Marshal(sessionData)
if err != nil {
common.ErrorResp(c, err, 400)
}
common.SuccessResp(c, gin.H{
"options": options,
"session": val,
})
}
func FinishAuthnRegistration(c *gin.Context) {
enabled := setting.GetBool(conf.WebauthnLoginEnabled)
if !enabled {
common.ErrorStrResp(c, "WebAuthn is not enabled", 403)
return
}
user := c.MustGet("user").(*model.User)
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)
return
}
var sessionData webauthn.SessionData
if err := json.Unmarshal(sessionDataBytes, &sessionData); err != nil {
common.ErrorResp(c, err, 400)
return
}
credential, err := authnInstance.FinishRegistration(user, sessionData, c.Request)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
err = db.RegisterAuthn(user, credential)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
err = op.DelUserCache(user.Username)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
common.SuccessResp(c, "Registered Successfully")
}
func DeleteAuthnLogin(c *gin.Context) {
user := c.MustGet("user").(*model.User)
type DeleteAuthnReq struct {
ID string `json:"id"`
}
var req DeleteAuthnReq
err := c.ShouldBind(&req)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
err = db.RemoveAuthn(user, req.ID)
err = op.DelUserCache(user.Username)
if err != nil {
common.ErrorResp(c, err, 400)
return
}
common.SuccessResp(c, "Deleted Successfully")
}
func GetAuthnCredentials(c *gin.Context) {
type WebAuthnCredentials struct {
ID []byte `json:"id"`
FingerPrint string `json:"fingerprint"`
}
user := c.MustGet("user").(*model.User)
credentials := user.WebAuthnCredentials()
res := make([]WebAuthnCredentials, 0, len(credentials))
for _, v := range credentials {
credential := WebAuthnCredentials{
ID: v.ID,
FingerPrint: fmt.Sprintf("% X", v.Authenticator.AAGUID),
}
res = append(res, credential)
}
common.SuccessResp(c, res)
}