feat(users): support predefined users
This commit is contained in:
parent
80342a7333
commit
52c8610271
8 changed files with 173 additions and 60 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
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 {
|
func (h *MultiUsersSvc) IsInited() bool {
|
||||||
|
|
|
@ -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"`
|
||||||
|
@ -9,22 +13,23 @@ type FSConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type UsersCfg struct {
|
type UsersCfg struct {
|
||||||
EnableAuth bool `json:"enableAuth" yaml:"enableAuth"`
|
EnableAuth bool `json:"enableAuth" yaml:"enableAuth"`
|
||||||
DefaultAdmin string `json:"defaultAdmin" yaml:"defaultAdmin" cfg:"env"`
|
DefaultAdmin string `json:"defaultAdmin" yaml:"defaultAdmin" cfg:"env"`
|
||||||
DefaultAdminPwd string `json:"defaultAdminPwd" yaml:"defaultAdminPwd" cfg:"env"`
|
DefaultAdminPwd string `json:"defaultAdminPwd" yaml:"defaultAdminPwd" cfg:"env"`
|
||||||
CookieTTL int `json:"cookieTTL" yaml:"cookieTTL"`
|
CookieTTL int `json:"cookieTTL" yaml:"cookieTTL"`
|
||||||
CookieSecure bool `json:"cookieSecure" yaml:"cookieSecure"`
|
CookieSecure bool `json:"cookieSecure" yaml:"cookieSecure"`
|
||||||
CookieHttpOnly bool `json:"cookieHttpOnly" yaml:"cookieHttpOnly"`
|
CookieHttpOnly bool `json:"cookieHttpOnly" yaml:"cookieHttpOnly"`
|
||||||
MinUserNameLen int `json:"minUserNameLen" yaml:"minUserNameLen"`
|
MinUserNameLen int `json:"minUserNameLen" yaml:"minUserNameLen"`
|
||||||
MinPwdLen int `json:"minPwdLen" yaml:"minPwdLen"`
|
MinPwdLen int `json:"minPwdLen" yaml:"minPwdLen"`
|
||||||
CaptchaWidth int `json:"captchaWidth" yaml:"captchaWidth"`
|
CaptchaWidth int `json:"captchaWidth" yaml:"captchaWidth"`
|
||||||
CaptchaHeight int `json:"captchaHeight" yaml:"captchaHeight"`
|
CaptchaHeight int `json:"captchaHeight" yaml:"captchaHeight"`
|
||||||
CaptchaEnabled bool `json:"captchaEnabled" yaml:"captchaEnabled"`
|
CaptchaEnabled bool `json:"captchaEnabled" yaml:"captchaEnabled"`
|
||||||
UploadSpeedLimit int `json:"uploadSpeedLimit" yaml:"uploadSpeedLimit"`
|
UploadSpeedLimit int `json:"uploadSpeedLimit" yaml:"uploadSpeedLimit"`
|
||||||
DownloadSpeedLimit int `json:"downloadSpeedLimit" yaml:"downloadSpeedLimit"`
|
DownloadSpeedLimit int `json:"downloadSpeedLimit" yaml:"downloadSpeedLimit"`
|
||||||
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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,50 +66,85 @@ 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{
|
||||||
if len(errs) > 0 {
|
{
|
||||||
t.Fatal(errs)
|
ID: 0,
|
||||||
} else if resp.StatusCode != 200 {
|
Name: adminName,
|
||||||
t.Fatal(resp.StatusCode)
|
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)
|
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 {
|
||||||
t.Fatal(resp.StatusCode)
|
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.SetPwd(adminPwd, adminNewPwd, token)
|
token := client.GetCookie(resp.Cookies(), su.TokenCookie)
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatal(errs)
|
|
||||||
} else if resp.StatusCode != 200 {
|
|
||||||
t.Fatal(resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, _, errs = usersCl.Logout(token)
|
resp, selfResp, errs := usersCl.Self(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 {
|
||||||
t.Fatal(resp.StatusCode)
|
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)
|
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 {
|
||||||
t.Fatal(resp.StatusCode)
|
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)
|
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 ||
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue