feat: support webauthn login (#4945)
* feat: support webauthn login * manually merge * fix: clear user cache after updating authn * decrease db size of Authn * change authn type to text * simplify code structure --------- Co-authored-by: Andy Hsu <i@nn.ci>
This commit is contained in:
25
internal/authn/authn.go
Normal file
25
internal/authn/authn.go
Normal file
@ -0,0 +1,25 @@
|
||||
package authn
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
)
|
||||
|
||||
func NewAuthnInstance(r *http.Request) (*webauthn.WebAuthn, error) {
|
||||
siteUrl, err := url.Parse(common.GetApiUrl(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webauthn.New(&webauthn.Config{
|
||||
RPDisplayName: setting.GetStr(conf.SiteTitle),
|
||||
RPID: siteUrl.Hostname(),
|
||||
//RPOrigin: siteUrl.String(),
|
||||
RPOrigins: []string{siteUrl.String()},
|
||||
// RPOrigin: "http://localhost:5173"
|
||||
})
|
||||
}
|
@ -139,6 +139,7 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
||||
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||
|
||||
// aria2 settings
|
||||
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
|
@ -41,6 +41,7 @@ const (
|
||||
OcrApi = "ocr_api"
|
||||
FilenameCharMapping = "filename_char_mapping"
|
||||
ForwardDirectLinkParams = "forward_direct_link_params"
|
||||
WebauthnLoginEnabled = "webauthn_login_enabled"
|
||||
|
||||
// index
|
||||
SearchIndex = "search_index"
|
||||
|
@ -29,5 +29,5 @@ func AutoMigrate(dst ...interface{}) error {
|
||||
}
|
||||
|
||||
func GetDb() *gorm.DB {
|
||||
return db;
|
||||
return db
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -59,3 +63,40 @@ func GetUsers(pageIndex, pageSize int) (users []model.User, count int64, err err
|
||||
func DeleteUserById(id uint) error {
|
||||
return errors.WithStack(db.Delete(&model.User{}, id).Error)
|
||||
}
|
||||
|
||||
func UpdateAuthn(userID uint, authn string) error {
|
||||
return db.Model(&model.User{ID: userID}).Update("authn", authn).Error
|
||||
}
|
||||
|
||||
func RegisterAuthn(u *model.User, credential *webauthn.Credential) error {
|
||||
if u == nil {
|
||||
return errors.New("user is nil")
|
||||
}
|
||||
exists := u.WebAuthnCredentials()
|
||||
if credential != nil {
|
||||
exists = append(exists, *credential)
|
||||
}
|
||||
res, err := utils.Json.Marshal(exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return UpdateAuthn(u.ID, string(res))
|
||||
}
|
||||
|
||||
func RemoveAuthn(u *model.User, id string) error {
|
||||
exists := u.WebAuthnCredentials()
|
||||
for i := 0; i < len(exists); i++ {
|
||||
idEncoded := base64.StdEncoding.EncodeToString(exists[i].ID)
|
||||
if idEncoded == id {
|
||||
exists[len(exists)-1], exists[i] = exists[i], exists[len(exists)-1]
|
||||
exists = exists[:len(exists)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
res, err := utils.Json.Marshal(exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return UpdateAuthn(u.ID, string(res))
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -41,6 +44,7 @@ type User struct {
|
||||
Permission int32 `json:"permission"`
|
||||
OtpSecret string `json:"-"`
|
||||
SsoID string `json:"sso_id"` // unique by sso platform
|
||||
Authn string `gorm:"type:text" json:"-"`
|
||||
}
|
||||
|
||||
func (u *User) IsGuest() bool {
|
||||
@ -130,3 +134,30 @@ func HashPwd(static string, salt string) string {
|
||||
func TwoHashPwd(password string, salt string) string {
|
||||
return HashPwd(StaticHash(password), salt)
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnID() []byte {
|
||||
bs := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bs, uint64(u.ID))
|
||||
return bs
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnName() string {
|
||||
return u.Username
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnDisplayName() string {
|
||||
return u.Username
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnCredentials() []webauthn.Credential {
|
||||
var res []webauthn.Credential
|
||||
err := json.Unmarshal([]byte(u.Authn), &res)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnIcon() string {
|
||||
return "https://alist.nn.ci/logo.svg"
|
||||
}
|
||||
|
Reference in New Issue
Block a user