fix(singleuser): fix bugs in single user handlers
This commit is contained in:
parent
31a1a331f7
commit
4d6e7ff938
10 changed files with 194 additions and 45 deletions
2
go.mod
2
go.mod
|
@ -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
1
go.sum
|
@ -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=
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
65
src/handlers/singleuserhdr/middlewares.go
Normal file
65
src/handlers/singleuserhdr/middlewares.go
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}`
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue