|
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 { |
|
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 { |
|
_, err = authnInstance.FinishDiscoverableLogin(func(_, userHandle []byte) (webauthn.User, error) { |
|
|
|
|
|
|
|
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) |
|
} |
|
|