nezha/cmd/dashboard/controller/jwt.go

184 lines
4.6 KiB
Go
Raw Normal View History

2024-10-20 00:09:16 +08:00
package controller
import (
2024-10-20 23:23:04 +08:00
"encoding/json"
2024-10-20 14:05:43 +08:00
"net/http"
2024-10-20 00:09:16 +08:00
"time"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
2024-10-21 12:11:02 +08:00
"golang.org/x/crypto/bcrypt"
2024-10-20 00:09:16 +08:00
"github.com/naiba/nezha/model"
2024-10-21 12:11:02 +08:00
"github.com/naiba/nezha/pkg/utils"
"github.com/naiba/nezha/service/singleton"
2024-10-20 00:09:16 +08:00
)
func initParams() *jwt.GinJWTMiddleware {
return &jwt.GinJWTMiddleware{
Realm: singleton.Conf.SiteName,
2024-10-20 23:23:04 +08:00
Key: []byte(singleton.Conf.JWTSecretKey),
2024-10-20 14:05:43 +08:00
CookieName: "nz-jwt",
2024-10-20 00:09:16 +08:00
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: model.CtxKeyAuthorizedUser,
PayloadFunc: payloadFunc(),
IdentityHandler: identityHandler(),
Authenticator: authenticator(),
Authorizator: authorizator(),
Unauthorized: unauthorized(),
2024-10-20 14:05:43 +08:00
TokenLookup: "header: Authorization, query: token, cookie: nz-jwt",
2024-10-20 00:09:16 +08:00
TokenHeadName: "Bearer",
TimeFunc: time.Now,
2024-10-20 14:05:43 +08:00
LoginResponse: func(c *gin.Context, code int, token string, expire time.Time) {
c.JSON(http.StatusOK, model.CommonResponse[model.LoginResponse]{
Success: true,
Data: model.LoginResponse{
Token: token,
Expire: expire.Format(time.RFC3339),
},
})
},
RefreshResponse: refreshResponse,
2024-10-20 00:09:16 +08:00
}
}
func payloadFunc() func(data interface{}) jwt.MapClaims {
return func(data interface{}) jwt.MapClaims {
2024-10-20 14:05:43 +08:00
if v, ok := data.(string); ok {
2024-10-20 00:09:16 +08:00
return jwt.MapClaims{
2024-10-20 14:05:43 +08:00
model.CtxKeyAuthorizedUser: v,
2024-10-20 00:09:16 +08:00
}
}
return jwt.MapClaims{}
}
}
func identityHandler() func(c *gin.Context) interface{} {
return func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
2024-10-20 14:05:43 +08:00
userId := claims[model.CtxKeyAuthorizedUser].(string)
var user model.User
if err := singleton.DB.First(&user, userId).Error; err != nil {
return nil
2024-10-20 00:09:16 +08:00
}
return &user
2024-10-20 00:09:16 +08:00
}
}
2024-10-20 14:05:43 +08:00
// User Login
// @Summary user login
// @Schemes
2024-10-20 14:05:43 +08:00
// @Description user login
// @Accept json
2024-10-20 23:23:04 +08:00
// @param loginRequest body model.LoginRequest true "Login Request"
// @Produce json
2024-10-20 14:05:43 +08:00
// @Success 200 {object} model.CommonResponse[model.LoginResponse]
// @Router /login [post]
2024-10-20 00:09:16 +08:00
func authenticator() func(c *gin.Context) (interface{}, error) {
return func(c *gin.Context) (interface{}, error) {
var loginVals model.LoginRequest
if err := c.ShouldBind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
var user model.User
2024-10-20 14:05:43 +08:00
if err := singleton.DB.Select("id", "password").Where("username = ?", loginVals.Username).First(&user).Error; err != nil {
return nil, jwt.ErrFailedAuthentication
2024-10-20 00:09:16 +08:00
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(loginVals.Password)); err != nil {
return nil, jwt.ErrFailedAuthentication
}
2024-10-20 14:05:43 +08:00
if err := singleton.DB.Model(&user).Update("login_expire", time.Now().Add(time.Hour)).Error; err != nil {
return nil, jwt.ErrFailedAuthentication
}
2024-10-21 12:11:02 +08:00
return utils.Itoa(user.ID), nil
2024-10-20 00:09:16 +08:00
}
}
func authorizator() func(data interface{}, c *gin.Context) bool {
return func(data interface{}, c *gin.Context) bool {
_, ok := data.(*model.User)
return ok
2024-10-20 00:09:16 +08:00
}
}
func unauthorized() func(c *gin.Context, code int, message string) {
return func(c *gin.Context, code int, message string) {
2024-10-20 14:05:43 +08:00
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
Success: false,
Error: "ApiErrorUnauthorized",
})
}
}
// Refresh token
// @Summary Refresh token
// @Security BearerAuth
// @Schemes
// @Description Refresh token
// @Tags auth required
// @Produce json
// @Success 200 {object} model.CommonResponse[model.LoginResponse]
// @Router /refresh_token [get]
func refreshResponse(c *gin.Context, code int, token string, expire time.Time) {
claims := jwt.ExtractClaims(c)
userId := claims[model.CtxKeyAuthorizedUser].(string)
if err := singleton.DB.Model(&model.User{}).Where("id = ?", userId).Update("login_expire", expire).Error; err != nil {
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
2024-10-20 00:09:16 +08:00
Success: false,
2024-10-20 14:05:43 +08:00
Error: "ApiErrorUnauthorized",
2024-10-20 00:09:16 +08:00
})
2024-10-20 14:05:43 +08:00
return
2024-10-20 00:09:16 +08:00
}
2024-10-20 14:05:43 +08:00
c.JSON(http.StatusOK, model.CommonResponse[model.LoginResponse]{
Success: true,
Data: model.LoginResponse{
Token: token,
Expire: expire.Format(time.RFC3339),
},
})
2024-10-20 00:09:16 +08:00
}
2024-10-20 23:23:04 +08:00
func optionalAuthMiddleware(mw *jwt.GinJWTMiddleware) func(c *gin.Context) {
2024-10-20 23:23:04 +08:00
return func(c *gin.Context) {
claims, err := mw.GetClaimsFromJWT(c)
if err != nil {
return
}
switch v := claims["exp"].(type) {
case nil:
return
case float64:
if int64(v) < mw.TimeFunc().Unix() {
return
}
case json.Number:
n, err := v.Int64()
if err != nil {
return
}
if n < mw.TimeFunc().Unix() {
return
}
default:
return
}
c.Set("JWT_PAYLOAD", claims)
identity := mw.IdentityHandler(c)
if identity != nil {
c.Set(mw.IdentityKey, identity)
}
c.Next()
}
}