github.com/kuoss/venti@v0.2.20/pkg/handler/auth.go (about) 1 package handler 2 3 import ( 4 "crypto/rand" 5 "fmt" 6 "net/http" 7 "strings" 8 "time" 9 10 "github.com/kuoss/common/logger" 11 "github.com/kuoss/venti/pkg/handler/api" 12 "github.com/kuoss/venti/pkg/model" 13 userService "github.com/kuoss/venti/pkg/service/user" 14 "gorm.io/gorm" 15 16 "github.com/gin-gonic/gin" 17 "golang.org/x/crypto/bcrypt" 18 ) 19 20 type authHandler struct { 21 // todo service to database 22 userService *userService.UserService 23 } 24 25 func NewAuthHandler(s *userService.UserService) *authHandler { 26 return &authHandler{s} 27 } 28 29 func (h *authHandler) Login(c *gin.Context) { 30 username := c.PostForm("username") 31 password := c.PostForm("password") 32 if username == "" { 33 api.ResponseError(c, api.ErrorUnauthorized, fmt.Errorf("username is empty")) 34 return 35 } 36 if password == "" { 37 api.ResponseError(c, api.ErrorUnauthorized, fmt.Errorf("password is empty")) 38 return 39 } 40 41 user, err := h.userService.FindByUsername(username) 42 if err != nil { 43 if err == gorm.ErrRecordNotFound { 44 api.ResponseError(c, api.ErrorUnauthorized, fmt.Errorf("username not found")) 45 return 46 } 47 api.ResponseError(c, api.ErrorUnauthorized, fmt.Errorf("FindByUsername err: %w", err)) 48 return 49 } 50 51 if !checkPassword(password, user.Hash) { 52 logger.Infof("User login failed.") 53 api.ResponseError(c, api.ErrorUnauthorized, fmt.Errorf("username or password is incorrect")) 54 return 55 } 56 57 user = issueToken(user) 58 err = h.userService.Save(user) 59 if err != nil { 60 logger.Errorf("update token err: %s", err.Error()) 61 api.ResponseError(c, api.ErrorInternal, fmt.Errorf("token save err: %w", err)) 62 return 63 } 64 65 logger.Infof("user '%s' logged in successfully.", user.Username) 66 c.JSON(200, gin.H{ 67 "message": "You are logged in.", 68 "token": user.Token, 69 "userID": user.ID, 70 "username": user.Username, 71 }) 72 } 73 74 func checkPassword(plain string, hashed string) bool { 75 return bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)) == nil 76 } 77 78 func issueToken(user model.User) model.User { 79 if user.Token != "" && user.TokenExpires.After(time.Now()) { 80 user.TokenExpires = time.Now().Add(48 * time.Hour) 81 } else { 82 b := make([]byte, 16) 83 _, _ = rand.Read(b) 84 token := fmt.Sprintf("%x", b) 85 user.Token = token 86 user.TokenExpires = time.Now().Add(48 * time.Hour) 87 } 88 return user 89 } 90 91 func (h *authHandler) Logout(c *gin.Context) { 92 //deleteTokenIfWeCan(c) 93 // token delete if we can 94 tokenFromHeader := c.GetHeader("Authorization") 95 if tokenFromHeader == "" || !strings.HasPrefix(tokenFromHeader, "Bearer ") { 96 // todo normal logout? 97 return 98 } 99 tokenFromHeader = strings.TrimPrefix(tokenFromHeader, "Bearer ") 100 userID := c.GetHeader("UserID") 101 102 user, err := h.userService.FindByUserIdAndToken(userID, tokenFromHeader) 103 if err != nil { 104 return 105 } 106 107 user.Token = "" 108 user.TokenExpires = time.Now().Add(-480 * time.Hour) 109 err = h.userService.Save(user) 110 if err != nil { 111 return 112 } 113 c.JSON(200, gin.H{ 114 "message": "You are logged out.", 115 }) 116 } 117 118 // TODO: add to handler routes check if we have to this on method 119 // only checks userid, Bearer tokene exist. not validation. 120 func (h *authHandler) HeaderRequired(c *gin.Context) { 121 token := c.GetHeader("Authorization") 122 if token == "" || !strings.HasPrefix(token, "Bearer ") { 123 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 124 "message": "valid token required", 125 }) 126 return 127 } 128 129 userID := c.GetHeader("UserID") 130 if userID == "" { 131 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 132 "message": "userId required", 133 }) 134 return 135 } 136 c.Next() 137 }