feat(users): support predefined users

This commit is contained in:
hexxa 2021-09-17 23:04:41 +08:00 committed by Hexxa
parent 80342a7333
commit 52c8610271
8 changed files with 173 additions and 60 deletions

View file

@ -39,6 +39,7 @@ Choose Language: English | [简体中文](./docs/README_zh-cn.md)
- Per-user space quota - Per-user space quota
- MISC - MISC
- Adaptive UI - Adaptive UI
- I18n support
- Cross-platform: support Linux, Mac and Windows - Cross-platform: support Linux, Mac and Windows

View file

@ -29,6 +29,10 @@ users:
cookieHttpOnly: true cookieHttpOnly: true
minUserNameLen: 2 minUserNameLen: 2
minPwdLen: 4 minPwdLen: 4
predefinedUsers:
- name: "demo"
pwd: "Quicksh@re"
role: "user"
workers: workers:
queueSize: 1024 queueSize: 1024
sleepCyc: 1 # in second sleepCyc: 1 # in second

View file

@ -29,6 +29,10 @@ users:
cookieHttpOnly: true cookieHttpOnly: true
minUserNameLen: 4 minUserNameLen: 4
minPwdLen: 6 minPwdLen: 6
predefinedUsers:
- name: "demo"
pwd: "Quicksh@re"
role: "user"
workers: workers:
queueSize: 1024 queueSize: 1024
sleepCyc: 1 # in second sleepCyc: 1 # in second

View file

@ -133,7 +133,58 @@ func (h *MultiUsersSvc) Init(adminName, adminPwd string) (string, error) {
// TODO: return "" for being compatible with singleuser service, should remove this // TODO: return "" for being compatible with singleuser service, should remove this
err = h.deps.Users().Init(adminName, adminPwd) err = h.deps.Users().Init(adminName, adminPwd)
if err != nil {
return "", err 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 { func (h *MultiUsersSvc) IsInited() bool {

View file

@ -1,6 +1,10 @@
package server package server
import "encoding/json" import (
"encoding/json"
"github.com/ihexxa/quickshare/src/userstore"
)
type FSConfig struct { type FSConfig struct {
Root string `json:"root"` Root string `json:"root"`
@ -25,6 +29,7 @@ type UsersCfg struct {
SpaceLimit int `json:"spaceLimit" yaml:"spaceLimit"` SpaceLimit int `json:"spaceLimit" yaml:"spaceLimit"`
LimiterCapacity int `json:"limiterCapacity" yaml:"limiterCapacity"` LimiterCapacity int `json:"limiterCapacity" yaml:"limiterCapacity"`
LimiterCyc int `json:"limiterCyc" yaml:"limiterCyc"` LimiterCyc int `json:"limiterCyc" yaml:"limiterCyc"`
PredefinedUsers []*userstore.UserCfg `json:"predefinedUsers" yaml:"predefinedUsers"`
} }
type Secrets struct { type Secrets struct {

View file

@ -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) 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) fileHdrs, err := fileshdr.NewFileHandlers(cfg, deps)

View file

@ -25,7 +25,14 @@ func TestUsersHandlers(t *testing.T) {
"downloadSpeedLimit": 409600, "downloadSpeedLimit": 409600,
"spaceLimit": 1024, "spaceLimit": 1024,
"limiterCapacity": 1000, "limiterCapacity": 1000,
"limiterCyc": 1000 "limiterCyc": 1000,
"predefinedUsers": [
{
"name": "demo",
"pwd": "Quicksh@re",
"role": "user"
}
]
}, },
"server": { "server": {
"debug": true, "debug": true,
@ -59,7 +66,37 @@ func TestUsersHandlers(t *testing.T) {
} }
t.Run("test users APIs: Login-Self-SetPwd-Logout-Login", func(t *testing.T) { t.Run("test users APIs: Login-Self-SetPwd-Logout-Login", func(t *testing.T) {
resp, _, errs := usersCl.Login(adminName, adminPwd) 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,
},
},
}
for _, user := range users {
usersCl := client.NewSingleUserClient(addr)
resp, _, errs := usersCl.Login(user.Name, user.Pwd)
if len(errs) > 0 { if len(errs) > 0 {
t.Fatal(errs) t.Fatal(errs)
} else if resp.StatusCode != 200 { } else if resp.StatusCode != 200 {
@ -73,18 +110,22 @@ func TestUsersHandlers(t *testing.T) {
t.Fatal(errs) t.Fatal(errs)
} else if resp.StatusCode != 200 { } else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode) t.Fatal(resp.StatusCode)
} else if selfResp.ID != "0" || } else if selfResp.Name != user.Name ||
selfResp.Name != adminName || selfResp.Role != user.Role ||
selfResp.Role != userstore.AdminRole ||
selfResp.UsedSpace != 0 || selfResp.UsedSpace != 0 ||
selfResp.Quota.SpaceLimit != 1024*1024*1024 || selfResp.Quota.UploadSpeedLimit != user.Quota.UploadSpeedLimit ||
selfResp.Quota.UploadSpeedLimit != 50*1024*1024 || selfResp.Quota.DownloadSpeedLimit != user.Quota.DownloadSpeedLimit ||
selfResp.Quota.DownloadSpeedLimit != 50*1024*1024 { selfResp.Quota.SpaceLimit != user.Quota.SpaceLimit {
// TODO: expose default values from userstore // TODO: expose default values from userstore
t.Fatalf("user infos don't match %v", selfResp) 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.SetPwd(adminPwd, adminNewPwd, token) resp, _, errs = usersCl.SetPwd(user.Pwd, adminNewPwd, token)
if len(errs) > 0 { if len(errs) > 0 {
t.Fatal(errs) t.Fatal(errs)
} else if resp.StatusCode != 200 { } else if resp.StatusCode != 200 {
@ -98,12 +139,13 @@ func TestUsersHandlers(t *testing.T) {
t.Fatal(resp.StatusCode) t.Fatal(resp.StatusCode)
} }
resp, _, errs = usersCl.Login(adminName, adminNewPwd) resp, _, errs = usersCl.Login(user.Name, adminNewPwd)
if len(errs) > 0 { if len(errs) > 0 {
t.Fatal(errs) t.Fatal(errs)
} else if resp.StatusCode != 200 { } else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode) t.Fatal(resp.StatusCode)
} }
}
}) })
t.Run("test users APIs: Login-AddUser-Logout-Login-Logout", func(t *testing.T) { t.Run("test users APIs: Login-AddUser-Logout-Login-Logout", func(t *testing.T) {
@ -198,7 +240,7 @@ func TestUsersHandlers(t *testing.T) {
t.Fatal(resp.StatusCode) 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))) t.Fatal(fmt.Errorf("incorrect users size (%d)", len(lsResp.Users)))
} }
for _, user := range lsResp.Users { for _, user := range lsResp.Users {
@ -264,7 +306,7 @@ func TestUsersHandlers(t *testing.T) {
} else if resp.StatusCode != 200 { } else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode) 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))) t.Fatal(fmt.Errorf("incorrect users size (%d)", len(lsResp.Users)))
} else if lsResp.Users[0].ID != 0 || } else if lsResp.Users[0].ID != 0 ||
lsResp.Users[0].Name != adminName || lsResp.Users[0].Name != adminName ||

View file

@ -39,6 +39,12 @@ type Quota struct {
DownloadSpeedLimit int `json:"downloadSpeedLimit"` DownloadSpeedLimit int `json:"downloadSpeedLimit"`
} }
type UserCfg struct {
Name string `json:"name"`
Role string `json:"role"`
Pwd string `json:"pwd"`
}
type User struct { type User struct {
ID uint64 `json:"id,string"` ID uint64 `json:"id,string"`
Name string `json:"name"` Name string `json:"name"`