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  }