mirror of https://github.com/Xhofe/alist
				
				
				
			
		
			
				
	
	
		
			239 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
| 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)
 | |
| 	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, "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)
 | |
| }
 |