diff --git a/src/handlers/singleuserhdr/handlers.go b/src/handlers/singleuserhdr/handlers.go deleted file mode 100644 index 371a0e5..0000000 --- a/src/handlers/singleuserhdr/handlers.go +++ /dev/null @@ -1,219 +0,0 @@ -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") - ErrInvalidConfig = errors.New("invalid user config") - UserParam = "user" - PwdParam = "pwd" - NewPwdParam = "newpwd" - RoleParam = "role" - ExpireParam = "expire" - InitTimeParam = "initTime" - TokenCookie = "tk" - AdminRole = "admin" - VisitorRole = "visitor" - InitNs = "usersInit" - UsersNs = "users" - RolesNs = "roles" -) - -type SimpleUserHandlers struct { - cfg gocfg.ICfg - deps *depidx.Deps -} - -func NewSimpleUserHandlers(cfg gocfg.ICfg, deps *depidx.Deps) (*SimpleUserHandlers, error) { - var err error - if err = deps.KV().AddNamespace(InitNs); err != nil { - return nil, err - } - if err = deps.KV().AddNamespace(UsersNs); err != nil { - return nil, err - } - if err = deps.KV().AddNamespace(RolesNs); err != nil { - return nil, err - } - - return &SimpleUserHandlers{ - cfg: cfg, - deps: deps, - }, nil -} - -func (h *SimpleUserHandlers) IsInited() bool { - _, ok := h.deps.KV().GetStringIn(InitNs, InitTimeParam) - return ok -} - -func (h *SimpleUserHandlers) Init(userName, pwd string) (string, error) { - if userName == "" { - return "", errors.New("user name can not be empty") - } - - pwdHash, err := bcrypt.GenerateFromPassword([]byte(pwd), 10) - if err != nil { - return "", err - } - - err = h.deps.KV().SetStringIn(UsersNs, userName, string(pwdHash)) - if err != nil { - return "", err - } - err = h.deps.KV().SetStringIn(RolesNs, userName, AdminRole) - if err != nil { - return "", err - } - err = h.deps.KV().SetStringIn(InitNs, InitTimeParam, fmt.Sprintf("%d", time.Now().Unix())) - if err != nil { - return "", err - } - - return pwd, nil -} - -type LoginReq struct { - User string `json:"user"` - Pwd string `json:"pwd"` -} - -func (h *SimpleUserHandlers) checkPwd(user, pwd string) error { - expectedHash, ok := h.deps.KV().GetStringIn(UsersNs, user) - if !ok { - return ErrInvalidConfig - } - - return bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(pwd)) -} - -func (h *SimpleUserHandlers) Login(c *gin.Context) { - req := &LoginReq{} - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(q.ErrResp(c, 500, err)) - return - } - - if err := h.checkPwd(req.User, req.Pwd); err != nil { - c.JSON(q.ErrResp(c, 500, err)) - return - } - - role, ok := h.deps.KV().GetStringIn(RolesNs, req.User) - if !ok { - c.JSON(q.ErrResp(c, 501, ErrInvalidConfig)) - return - } - ttl := h.cfg.GrabInt("Users.CookieTTL") - token, err := h.deps.Token().ToToken(map[string]string{ - UserParam: req.User, - RoleParam: role, - ExpireParam: fmt.Sprintf("%d", time.Now().Unix()+int64(ttl)), - }) - if err != nil { - c.JSON(q.ErrResp(c, 500, err)) - return - } - - secure := h.cfg.GrabBool("Users.CookieSecure") - httpOnly := h.cfg.GrabBool("Users.CookieHttpOnly") - c.SetCookie(TokenCookie, token, ttl, "/", "", secure, httpOnly) - - c.JSON(q.Resp(200)) -} - -type LogoutReq struct { -} - -func (h *SimpleUserHandlers) Logout(c *gin.Context) { - // token alreay verified in the authn middleware - secure := h.cfg.GrabBool("Users.CookieSecure") - httpOnly := h.cfg.GrabBool("Users.CookieHttpOnly") - c.SetCookie(TokenCookie, "", 0, "/", "", secure, httpOnly) - c.JSON(q.Resp(200)) -} - -func (h *SimpleUserHandlers) IsAuthed(c *gin.Context) { - // token alreay verified in the authn middleware - c.JSON(q.Resp(200)) -} - -type SetPwdReq struct { - OldPwd string `json:"oldPwd"` - NewPwd string `json:"newPwd"` -} - -func (h *SimpleUserHandlers) SetPwd(c *gin.Context) { - req := &SetPwdReq{} - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(q.ErrResp(c, 400, err)) - return - } else if req.OldPwd == req.NewPwd { - c.JSON(q.ErrResp(c, 400, errors.New("password is not updated"))) - return - } - - claims, err := h.getUserInfo(c) - if err != nil { - c.JSON(q.ErrResp(c, 401, err)) - return - } - - expectedHash, ok := h.deps.KV().GetStringIn(UsersNs, claims[UserParam]) - if !ok { - c.JSON(q.ErrResp(c, 500, ErrInvalidConfig)) - return - } - - err = bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(req.OldPwd)) - if err != nil { - c.JSON(q.ErrResp(c, 401, ErrInvalidUser)) - return - } - - newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPwd), 10) - if err != nil { - c.JSON(q.ErrResp(c, 500, errors.New("fail to set password"))) - return - } - err = h.deps.KV().SetStringIn(UsersNs, claims[UserParam], string(newHash)) - if err != nil { - c.JSON(q.ErrResp(c, 500, ErrInvalidConfig)) - return - } - - c.JSON(q.Resp(200)) -} - -func (h *SimpleUserHandlers) getUserInfo(c *gin.Context) (map[string]string, error) { - tokenStr, err := c.Cookie(TokenCookie) - if err != nil { - return nil, err - } - claims, err := h.deps.Token().FromToken( - tokenStr, - map[string]string{ - UserParam: "", - RoleParam: "", - ExpireParam: "", - }, - ) - if err != nil { - return nil, err - } else if claims[UserParam] == "" { - return nil, ErrInvalidConfig - } - - return claims, nil -} diff --git a/src/handlers/singleuserhdr/middlewares.go b/src/handlers/singleuserhdr/middlewares.go deleted file mode 100644 index c960eaa..0000000 --- a/src/handlers/singleuserhdr/middlewares.go +++ /dev/null @@ -1,84 +0,0 @@ -package singleuserhdr - -import ( - "errors" - "strconv" - "strings" - "time" - - "github.com/gin-gonic/gin" - q "github.com/ihexxa/quickshare/src/handlers" -) - -var exposedAPIs = map[string]bool{ - "Login-fm": true, - "Health-fm": true, -} - -var publicRootPath = "/" -var publicStaticPath = "/static" - -func IsPublicPath(accessPath string) bool { - return accessPath == publicRootPath || strings.HasPrefix(accessPath, publicStaticPath) -} - -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 - } - accessPath := c.Request.URL.String() - - enableAuth := h.cfg.GrabBool("Users.EnableAuth") - if enableAuth && !exposedAPIs[handlerName] && !IsPublicPath(accessPath) { - token, err := c.Cookie(TokenCookie) - if err != nil { - c.AbortWithStatusJSON(q.ErrResp(c, 401, err)) - return - } - - claims := map[string]string{ - UserParam: "", - RoleParam: "", - ExpireParam: "", - } - - claims, err = h.deps.Token().FromToken(token, claims) - if err != nil { - c.AbortWithStatusJSON(q.ErrResp(c, 401, err)) - return - } - for key, val := range claims { - c.Set(key, val) - } - - now := time.Now().Unix() - expire, err := strconv.ParseInt(claims[ExpireParam], 10, 64) - if err != nil || expire <= now { - c.AbortWithStatusJSON(q.ErrResp(c, 401, err)) - return - } - - // visitor is only allowed to download - if claims[RoleParam] != AdminRole && handlerName != "Download-fm" { - c.AbortWithStatusJSON(q.ErrResp(c, 401, errors.New("not allowed"))) - return - } - } else { - // this is for UploadMgr to get user info to get related namespace - c.Set(UserParam, "quickshare_anonymous") - } - - c.Next() - } -}