fix(singleuser): fix bugs in single user handlers

This commit is contained in:
hexxa 2020-12-05 22:00:20 +08:00
parent 31a1a331f7
commit 4d6e7ff938
10 changed files with 194 additions and 45 deletions

2
go.mod
View file

@ -12,6 +12,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/robbert229/jwt v2.0.0+incompatible
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
moul.io/http2curl v1.0.0 // indirect
)
@ -19,4 +20,3 @@ require (
replace github.com/ihexxa/gocfg => /home/hexxa/ws/github.com/ihexxa/gocfg
replace github.com/ihexxa/multipart => /home/hexxa/ws/github.com/ihexxa/multipart

1
go.sum
View file

@ -89,6 +89,7 @@ go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=

View file

@ -2,15 +2,30 @@ package singleuserhdr
import (
"errors"
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/ihexxa/gocfg"
"golang.org/x/crypto/bcrypt"
"github.com/ihexxa/quickshare/src/depidx"
q "github.com/ihexxa/quickshare/src/handlers"
)
var ErrInvalidUser = errors.New("invalid user name or password")
var (
ErrInvalidUser = errors.New("invalid user name or password")
ErrInvalidConfig = errors.New("invalid user config")
UserParam = "user"
PwdParam = "pwd"
RoleParam = "role"
ExpireParam = "expire"
TokenCookie = "tk"
AdminRole = "admin"
VisitorRole = "visitor"
UsersNamespace = "users"
RolesNamespace = "roles"
)
type SimpleUserHandlers struct {
cfg gocfg.ICfg
@ -24,52 +39,52 @@ func NewSimpleUserHandlers(cfg gocfg.ICfg, deps *depidx.Deps) *SimpleUserHandler
}
}
func (hdr *SimpleUserHandlers) Login(c *gin.Context) {
userName := c.Query("username")
pwd := c.Query("pwd")
if userName == "" || pwd == "" {
c.JSON(q.ErrResp(c, 400, ErrInvalidUser))
return
}
expectedName, ok1 := hdr.deps.KV().GetString("username")
expectedPwd, ok2 := hdr.deps.KV().GetString("pwd")
func (h *SimpleUserHandlers) Login(c *gin.Context) {
user, ok1 := c.GetPostForm(UserParam)
pwd, ok2 := c.GetPostForm(PwdParam)
if !ok1 || !ok2 {
c.JSON(q.ErrResp(c, 400, ErrInvalidUser))
c.JSON(q.ErrResp(c, 401, ErrInvalidUser))
return
}
if userName != expectedName || pwd != expectedPwd {
c.JSON(q.ErrResp(c, 400, ErrInvalidUser))
expectedHash, ok := h.deps.KV().GetStringIn(UsersNamespace, user)
if !ok {
c.JSON(q.ErrResp(c, 500, ErrInvalidConfig))
return
}
token, err := hdr.deps.Token().ToToken(map[string]string{
"username": expectedName,
err := bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(pwd))
if err != nil {
c.JSON(q.ErrResp(c, 401, ErrInvalidUser))
return
}
role, ok := h.deps.KV().GetStringIn(RolesNamespace, user)
if !ok {
c.JSON(q.ErrResp(c, 500, ErrInvalidConfig))
return
}
ttl := h.cfg.GrabInt("Users.CookieTTL")
token, err := h.deps.Token().ToToken(map[string]string{
UserParam: user,
RoleParam: role,
ExpireParam: fmt.Sprintf("%d", time.Now().Unix()+int64(ttl)),
})
if err != nil {
c.JSON(q.ErrResp(c, 500, err))
return
}
// TODO: use config
c.SetCookie("token", token, 3600, "/", "localhost", false, true)
hostname := h.cfg.GrabString("Server.Host")
secure := h.cfg.GrabBool("Users.CookieSecure")
httpOnly := h.cfg.GrabBool("Users.CookieHttpOnly")
c.SetCookie(TokenCookie, token, ttl, "/", hostname, secure, httpOnly)
c.JSON(q.Resp(200))
}
func (hdr *SimpleUserHandlers) Logout(c *gin.Context) {
token, err := c.Cookie("token")
if err != nil {
c.JSON(q.ErrResp(c, 400, err))
return
}
// TODO: // check if token expired
_, err = hdr.deps.Token().FromToken(token, map[string]string{"token": ""})
if err != nil {
c.JSON(q.ErrResp(c, 400, err))
return
}
c.SetCookie("token", "", 0, "/", "localhost", false, true)
func (h *SimpleUserHandlers) Logout(c *gin.Context) {
// token alreay verified in the authn middleware
c.SetCookie(TokenCookie, "", 0, "/", "nohost", false, true)
c.JSON(q.Resp(200))
}

View file

@ -0,0 +1,65 @@
package singleuserhdr
import (
"errors"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
q "github.com/ihexxa/quickshare/src/handlers"
)
func GetHandlerName(fullname string) (string, error) {
parts := strings.Split(fullname, ".")
if len(parts) == 0 {
return "", errors.New("invalid handler name")
}
return parts[len(parts)-1], nil
}
func (h *SimpleUserHandlers) Auth() gin.HandlerFunc {
return func(c *gin.Context) {
handlerName, err := GetHandlerName(c.HandlerName())
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
return
}
// TODO: may also check the path
enableAuth := h.cfg.GrabBool("Users.EnableAuth")
if enableAuth && handlerName != "Login-fm" {
token, err := c.Cookie(TokenCookie)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
return
}
claims := map[string]string{
UserParam: "",
RoleParam: "",
ExpireParam: "",
}
_, err = h.deps.Token().FromToken(token, claims)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
return
}
now := time.Now().Unix()
expire, err := strconv.ParseInt(claims[ExpireParam], 10, 64)
if err != nil || expire <= now {
c.JSON(q.ErrResp(c, 401, err))
return
}
// visitor is only allowed to download
if claims[UserParam] != AdminRole && handlerName != "Download-fm" {
c.JSON(q.ErrResp(c, 401, err))
return
}
}
c.Next()
}
}

View file

@ -27,6 +27,7 @@ func New(dbPath string, maxStrLen int) *BoltPvd {
buckets := []string{"bools", "ints", "int64s", "floats", "strings", "locks"}
for _, bucketName := range buckets {
// TODO: should return err
db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketName))
if b != nil {
@ -48,6 +49,18 @@ func New(dbPath string, maxStrLen int) *BoltPvd {
}
}
func (bp *BoltPvd) AddNamespace(nsName string) error {
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(nsName))
if b != nil {
return nil
}
_, err := tx.CreateBucket([]byte(nsName))
return err
})
}
func (bp *BoltPvd) Close() error {
return bp.db.Close()
}
@ -223,3 +236,26 @@ func (bp *BoltPvd) Unlock(key string) error {
return kvstore.ErrNoLock
})
}
func (bp *BoltPvd) GetStringIn(namespace, key string) (string, bool) {
buf, ok, length := make([]byte, bp.maxStrLen), false, bp.maxStrLen
bp.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(namespace))
v := b.Get([]byte(key))
length = copy(buf, v)
ok = v != nil
return nil
})
return string(buf[:length]), ok
}
func (bp *BoltPvd) SetStringIn(namespace, key, val string) error {
if len(val) > bp.maxStrLen {
return fmt.Errorf("can not set string value longer than %d", bp.maxStrLen)
}
return bp.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(namespace))
return b.Put([]byte(key), []byte(val))
})
}

View file

@ -6,6 +6,7 @@ var ErrLocked = errors.New("already locked")
var ErrNoLock = errors.New("no lock to unlock")
type IKVStore interface {
AddNamespace(nsName string) error
GetBool(key string) (bool, bool)
SetBool(key string, val bool) error
DelBool(key string) error
@ -19,8 +20,10 @@ type IKVStore interface {
SetFloat(key string, val float64) error
DelFloat(key string) error
GetString(key string) (string, bool)
SetString(key string, val string) error
SetString(key, val string) error
DelString(key string) error
GetStringIn(namespace, key string) (string, bool)
SetStringIn(namespace, key, val string) error
TryLock(key string) error
Unlock(key string) error
}

View file

@ -6,13 +6,21 @@ type FSConfig struct {
OpenTTL int `json:"openTTL"`
}
type UsersCfg struct {
EnableAuth bool `json:"enableAuth"`
CookieTTL int `json:"cookieTTL"`
CookieSecure bool `json:"cookieSecure"`
CookieHttpOnly bool `json:"cookieHttpOnly"`
}
type Secrets struct {
TokenSecret string `json:"tokenSecret" cfg:"env"`
}
type ServerCfg struct {
Debug bool `json:"debug"`
Addr string `json:"addr"`
Host string `json:"host"`
Port int `json:"port"`
ReadTimeout int `json:"readTimeout"`
WriteTimeout int `json:"writeTimeout"`
MaxHeaderBytes int `json:"maxHeaderBytes"`
@ -22,6 +30,7 @@ type Config struct {
Fs *FSConfig `json:"fs"`
Secrets *Secrets `json:"secrets"`
Server *ServerCfg `json:"server"`
Users *UsersCfg `json:"users"`
}
func NewEmptyConfig() *Config {
@ -35,12 +44,19 @@ func NewDefaultConfig() *Config {
OpensLimit: 128,
OpenTTL: 60, // 1 min
},
Users: &UsersCfg{
EnableAuth: true,
CookieTTL: 3600 * 24 * 7, // 1 week
CookieSecure: false,
CookieHttpOnly: true,
},
Secrets: &Secrets{
TokenSecret: "",
},
Server: &ServerCfg{
Debug: false,
Addr: "127.0.0.1:8888",
Host: "127.0.0.1",
Port: 8888,
ReadTimeout: 2000,
WriteTimeout: 2000,
MaxHeaderBytes: 512,

Binary file not shown.

View file

@ -43,7 +43,7 @@ func NewServer(cfg gocfg.ICfg) (*Server, error) {
srv := &http.Server{
// TODO: set more options
Addr: cfg.GrabString("Server.Addr"),
Addr: fmt.Sprintf("%s:%d", cfg.GrabString("Server.Host"), cfg.GrabInt("Server.Port")),
Handler: router,
ReadTimeout: time.Duration(cfg.GrabInt("Server.ReadTimeout")) * time.Millisecond,
WriteTimeout: time.Duration(cfg.GrabInt("Server.WriteTimeout")) * time.Millisecond,
@ -84,11 +84,17 @@ func initDeps(cfg gocfg.ICfg) *depidx.Deps {
opensLimit := cfg.GrabInt("Fs.OpensLimit")
openTTL := cfg.GrabInt("Fs.OpenTTL")
ider := simpleidgen.New()
filesystem := local.NewLocalFS(rootPath, 0660, opensLimit, openTTL)
jwtEncDec := jwt.NewJWTEncDec(secret)
logger := simplelog.NewSimpleLogger()
kv := boltdbpvd.New(".", 1024)
ider := simpleidgen.New()
if err := kv.AddNamespace(singleuserhdr.UsersNamespace); err != nil {
panic(err)
}
if err := kv.AddNamespace(singleuserhdr.RolesNamespace); err != nil {
panic(err)
}
deps := depidx.NewDeps(cfg)
deps.SetFS(filesystem)
@ -107,15 +113,19 @@ func initDeps(cfg gocfg.ICfg) *depidx.Deps {
}
func addHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.Engine, error) {
userHdrs := singleuserhdr.NewSimpleUserHandlers(cfg, deps)
fileHdrs, err := fileshdr.NewFileHandlers(cfg, deps)
// middleware
router.Use(userHdrs.Auth())
v1 := router.Group("/v1")
users := v1.Group("/users")
userHdrs := singleuserhdr.NewSimpleUserHandlers(cfg, deps)
users.POST("/login", userHdrs.Login)
users.POST("/logout", userHdrs.Logout)
filesSvc := v1.Group("/fs")
fileHdrs, err := fileshdr.NewFileHandlers(cfg, deps)
if err != nil {
panic(err)
}

View file

@ -36,11 +36,14 @@ func TestFileHandlers(t *testing.T) {
root := "./testData"
chunkSize := 2
config := `{
"Server": {
"Debug": true
"users": {
"enableAuth": false
},
"FS": {
"Root": "./testData"
"server": {
"debug": true
},
"fs": {
"root": "./testData"
}
}`