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 }