From 52c8610271b3c1ab7ce8ac14fba5d138f674a0c8 Mon Sep 17 00:00:00 2001 From: hexxa Date: Fri, 17 Sep 2021 23:04:41 +0800 Subject: [PATCH] feat(users): support predefined users --- README.md | 1 + configs/dev.yml | 4 + configs/docker.yml | 4 + src/handlers/multiusers/handlers.go | 53 +++++++++++- src/server/config.go | 39 +++++---- src/server/server.go | 2 +- src/server/server_users_test.go | 124 +++++++++++++++++++--------- src/userstore/user_store.go | 6 ++ 8 files changed, 173 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index a8ce3cd..c5054a1 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Choose Language: English | [简体中文](./docs/README_zh-cn.md) - Per-user space quota - MISC - Adaptive UI + - I18n support - Cross-platform: support Linux, Mac and Windows diff --git a/configs/dev.yml b/configs/dev.yml index 8a2d5a0..2c1a476 100644 --- a/configs/dev.yml +++ b/configs/dev.yml @@ -29,6 +29,10 @@ users: cookieHttpOnly: true minUserNameLen: 2 minPwdLen: 4 + predefinedUsers: + - name: "demo" + pwd: "Quicksh@re" + role: "user" workers: queueSize: 1024 sleepCyc: 1 # in second diff --git a/configs/docker.yml b/configs/docker.yml index f608c7e..b015c22 100644 --- a/configs/docker.yml +++ b/configs/docker.yml @@ -29,6 +29,10 @@ users: cookieHttpOnly: true minUserNameLen: 4 minPwdLen: 6 + predefinedUsers: + - name: "demo" + pwd: "Quicksh@re" + role: "user" workers: queueSize: 1024 sleepCyc: 1 # in second diff --git a/src/handlers/multiusers/handlers.go b/src/handlers/multiusers/handlers.go index 0210b62..d558462 100644 --- a/src/handlers/multiusers/handlers.go +++ b/src/handlers/multiusers/handlers.go @@ -133,7 +133,58 @@ func (h *MultiUsersSvc) Init(adminName, adminPwd string) (string, error) { // TODO: return "" for being compatible with singleuser service, should remove this err = h.deps.Users().Init(adminName, adminPwd) - return "", err + if err != nil { + return "", err + } + + usersInterface, ok := h.cfg.Slice("Users.PredefinedUsers") + spaceLimit := int64(h.cfg.IntOr("Users.SpaceLimit", 100*1024*1024)) + uploadSpeedLimit := h.cfg.IntOr("Users.UploadSpeedLimit", 100*1024) + downloadSpeedLimit := h.cfg.IntOr("Users.DownloadSpeedLimit", 100*1024) + if ok { + userCfgs, ok := usersInterface.([]*userstore.UserCfg) + if !ok { + return "", fmt.Errorf("predefined user is invalid: %s", err) + } + for _, userCfg := range userCfgs { + // TODO: following operations must be atomic + // TODO: check if the folders already exists + fsRootFolder := q.FsRootPath(userCfg.Name, "/") + if err = h.deps.FS().MkdirAll(fsRootFolder); err != nil { + + return "", err + } + uploadFolder := q.UploadFolder(userCfg.Name) + if err = h.deps.FS().MkdirAll(uploadFolder); err != nil { + return "", err + } + + pwdHash, err := bcrypt.GenerateFromPassword([]byte(userCfg.Pwd), 10) + if err != nil { + return "", err + } + + user := &userstore.User{ + ID: h.deps.ID().Gen(), + Name: userCfg.Name, + Pwd: string(pwdHash), + Role: userCfg.Role, + Quota: &userstore.Quota{ + SpaceLimit: spaceLimit, + UploadSpeedLimit: uploadSpeedLimit, + DownloadSpeedLimit: downloadSpeedLimit, + }, + } + + err = h.deps.Users().AddUser(user) + if err != nil { + h.deps.Log().Warn("warning: failed to add user(%s): %s", user, err) + return "", err + } + h.deps.Log().Infof("user(%s) is added", user.Name) + } + } + return "", nil } func (h *MultiUsersSvc) IsInited() bool { diff --git a/src/server/config.go b/src/server/config.go index 355706e..ed9b3f9 100644 --- a/src/server/config.go +++ b/src/server/config.go @@ -1,6 +1,10 @@ package server -import "encoding/json" +import ( + "encoding/json" + + "github.com/ihexxa/quickshare/src/userstore" +) type FSConfig struct { Root string `json:"root"` @@ -9,22 +13,23 @@ type FSConfig struct { } type UsersCfg struct { - EnableAuth bool `json:"enableAuth" yaml:"enableAuth"` - DefaultAdmin string `json:"defaultAdmin" yaml:"defaultAdmin" cfg:"env"` - DefaultAdminPwd string `json:"defaultAdminPwd" yaml:"defaultAdminPwd" cfg:"env"` - CookieTTL int `json:"cookieTTL" yaml:"cookieTTL"` - CookieSecure bool `json:"cookieSecure" yaml:"cookieSecure"` - CookieHttpOnly bool `json:"cookieHttpOnly" yaml:"cookieHttpOnly"` - MinUserNameLen int `json:"minUserNameLen" yaml:"minUserNameLen"` - MinPwdLen int `json:"minPwdLen" yaml:"minPwdLen"` - CaptchaWidth int `json:"captchaWidth" yaml:"captchaWidth"` - CaptchaHeight int `json:"captchaHeight" yaml:"captchaHeight"` - CaptchaEnabled bool `json:"captchaEnabled" yaml:"captchaEnabled"` - UploadSpeedLimit int `json:"uploadSpeedLimit" yaml:"uploadSpeedLimit"` - DownloadSpeedLimit int `json:"downloadSpeedLimit" yaml:"downloadSpeedLimit"` - SpaceLimit int `json:"spaceLimit" yaml:"spaceLimit"` - LimiterCapacity int `json:"limiterCapacity" yaml:"limiterCapacity"` - LimiterCyc int `json:"limiterCyc" yaml:"limiterCyc"` + EnableAuth bool `json:"enableAuth" yaml:"enableAuth"` + DefaultAdmin string `json:"defaultAdmin" yaml:"defaultAdmin" cfg:"env"` + DefaultAdminPwd string `json:"defaultAdminPwd" yaml:"defaultAdminPwd" cfg:"env"` + CookieTTL int `json:"cookieTTL" yaml:"cookieTTL"` + CookieSecure bool `json:"cookieSecure" yaml:"cookieSecure"` + CookieHttpOnly bool `json:"cookieHttpOnly" yaml:"cookieHttpOnly"` + MinUserNameLen int `json:"minUserNameLen" yaml:"minUserNameLen"` + MinPwdLen int `json:"minPwdLen" yaml:"minPwdLen"` + CaptchaWidth int `json:"captchaWidth" yaml:"captchaWidth"` + CaptchaHeight int `json:"captchaHeight" yaml:"captchaHeight"` + CaptchaEnabled bool `json:"captchaEnabled" yaml:"captchaEnabled"` + UploadSpeedLimit int `json:"uploadSpeedLimit" yaml:"uploadSpeedLimit"` + DownloadSpeedLimit int `json:"downloadSpeedLimit" yaml:"downloadSpeedLimit"` + SpaceLimit int `json:"spaceLimit" yaml:"spaceLimit"` + LimiterCapacity int `json:"limiterCapacity" yaml:"limiterCapacity"` + LimiterCyc int `json:"limiterCyc" yaml:"limiterCyc"` + PredefinedUsers []*userstore.UserCfg `json:"predefinedUsers" yaml:"predefinedUsers"` } type Secrets struct { diff --git a/src/server/server.go b/src/server/server.go index 67c98f5..02377dc 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -182,7 +182,7 @@ func initHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.E return nil, fmt.Errorf("init admin error: %w", err) } - deps.Log().Infof("user (%s) is created\n", adminName) + deps.Log().Infof("admin(%s) is created", adminName) } fileHdrs, err := fileshdr.NewFileHandlers(cfg, deps) diff --git a/src/server/server_users_test.go b/src/server/server_users_test.go index 33aafc8..173c28b 100644 --- a/src/server/server_users_test.go +++ b/src/server/server_users_test.go @@ -25,7 +25,14 @@ func TestUsersHandlers(t *testing.T) { "downloadSpeedLimit": 409600, "spaceLimit": 1024, "limiterCapacity": 1000, - "limiterCyc": 1000 + "limiterCyc": 1000, + "predefinedUsers": [ + { + "name": "demo", + "pwd": "Quicksh@re", + "role": "user" + } + ] }, "server": { "debug": true, @@ -59,50 +66,85 @@ func TestUsersHandlers(t *testing.T) { } t.Run("test users APIs: Login-Self-SetPwd-Logout-Login", func(t *testing.T) { - resp, _, errs := usersCl.Login(adminName, adminPwd) - if len(errs) > 0 { - t.Fatal(errs) - } else if resp.StatusCode != 200 { - t.Fatal(resp.StatusCode) + users := []*userstore.User{ + { + ID: 0, + Name: adminName, + Pwd: adminPwd, + Role: userstore.AdminRole, + UsedSpace: 0, + Quota: &userstore.Quota{ + UploadSpeedLimit: 50 * 1024 * 1024, + DownloadSpeedLimit: 50 * 1024 * 1024, + SpaceLimit: 1024 * 1024 * 1024, + }, + }, + { + ID: 0, + Name: "demo", + Pwd: "Quicksh@re", + Role: userstore.UserRole, + UsedSpace: 0, + Quota: &userstore.Quota{ + UploadSpeedLimit: 409600, + DownloadSpeedLimit: 409600, + SpaceLimit: 1024, + }, + }, } - token := client.GetCookie(resp.Cookies(), su.TokenCookie) + for _, user := range users { + usersCl := client.NewSingleUserClient(addr) - resp, selfResp, errs := usersCl.Self(token) - if len(errs) > 0 { - t.Fatal(errs) - } else if resp.StatusCode != 200 { - t.Fatal(resp.StatusCode) - } else if selfResp.ID != "0" || - selfResp.Name != adminName || - selfResp.Role != userstore.AdminRole || - selfResp.UsedSpace != 0 || - selfResp.Quota.SpaceLimit != 1024*1024*1024 || - selfResp.Quota.UploadSpeedLimit != 50*1024*1024 || - selfResp.Quota.DownloadSpeedLimit != 50*1024*1024 { - // TODO: expose default values from userstore - t.Fatalf("user infos don't match %v", selfResp) - } + resp, _, errs := usersCl.Login(user.Name, user.Pwd) + if len(errs) > 0 { + t.Fatal(errs) + } else if resp.StatusCode != 200 { + t.Fatal(resp.StatusCode) + } - resp, _, errs = usersCl.SetPwd(adminPwd, adminNewPwd, token) - if len(errs) > 0 { - t.Fatal(errs) - } else if resp.StatusCode != 200 { - t.Fatal(resp.StatusCode) - } + token := client.GetCookie(resp.Cookies(), su.TokenCookie) - resp, _, errs = usersCl.Logout(token) - if len(errs) > 0 { - t.Fatal(errs) - } else if resp.StatusCode != 200 { - t.Fatal(resp.StatusCode) - } + resp, selfResp, errs := usersCl.Self(token) + if len(errs) > 0 { + t.Fatal(errs) + } else if resp.StatusCode != 200 { + t.Fatal(resp.StatusCode) + } else if selfResp.Name != user.Name || + selfResp.Role != user.Role || + selfResp.UsedSpace != 0 || + selfResp.Quota.UploadSpeedLimit != user.Quota.UploadSpeedLimit || + selfResp.Quota.DownloadSpeedLimit != user.Quota.DownloadSpeedLimit || + selfResp.Quota.SpaceLimit != user.Quota.SpaceLimit { + // TODO: expose default values from userstore + t.Fatalf("user infos don't match %v", selfResp) + } + if selfResp.Role == userstore.AdminRole { + if selfResp.ID != "0" { + t.Fatalf("user id don't match %v", selfResp) + } + } - resp, _, errs = usersCl.Login(adminName, adminNewPwd) - if len(errs) > 0 { - t.Fatal(errs) - } else if resp.StatusCode != 200 { - t.Fatal(resp.StatusCode) + resp, _, errs = usersCl.SetPwd(user.Pwd, adminNewPwd, token) + if len(errs) > 0 { + t.Fatal(errs) + } else if resp.StatusCode != 200 { + t.Fatal(resp.StatusCode) + } + + resp, _, errs = usersCl.Logout(token) + if len(errs) > 0 { + t.Fatal(errs) + } else if resp.StatusCode != 200 { + t.Fatal(resp.StatusCode) + } + + resp, _, errs = usersCl.Login(user.Name, adminNewPwd) + if len(errs) > 0 { + t.Fatal(errs) + } else if resp.StatusCode != 200 { + t.Fatal(resp.StatusCode) + } } }) @@ -198,7 +240,7 @@ func TestUsersHandlers(t *testing.T) { t.Fatal(resp.StatusCode) } - if len(lsResp.Users) != 2 { + if len(lsResp.Users) != 3 { t.Fatal(fmt.Errorf("incorrect users size (%d)", len(lsResp.Users))) } for _, user := range lsResp.Users { @@ -264,7 +306,7 @@ func TestUsersHandlers(t *testing.T) { } else if resp.StatusCode != 200 { t.Fatal(resp.StatusCode) } - if len(lsResp.Users) != 1 { + if len(lsResp.Users) != 2 { t.Fatal(fmt.Errorf("incorrect users size (%d)", len(lsResp.Users))) } else if lsResp.Users[0].ID != 0 || lsResp.Users[0].Name != adminName || diff --git a/src/userstore/user_store.go b/src/userstore/user_store.go index 4c4ae47..a0f5146 100644 --- a/src/userstore/user_store.go +++ b/src/userstore/user_store.go @@ -39,6 +39,12 @@ type Quota struct { DownloadSpeedLimit int `json:"downloadSpeedLimit"` } +type UserCfg struct { + Name string `json:"name"` + Role string `json:"role"` + Pwd string `json:"pwd"` +} + type User struct { ID uint64 `json:"id,string"` Name string `json:"name"`