refactor: login & refresh token

This commit is contained in:
naiba 2024-10-20 14:05:43 +08:00
parent 41391989e7
commit 53f1abb7c8
10 changed files with 137 additions and 97 deletions

View File

@ -99,7 +99,7 @@ func (v *apiV1) monitorHistoriesById(c *gin.Context) {
}
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
var isViewPasswordVerfied bool
authorized := isMember || isViewPasswordVerfied
if server.HideForGuest && !authorized {

View File

@ -128,7 +128,7 @@ func (cp *commonPage) network(c *gin.Context) {
monitorHistories := singleton.MonitorAPI.GetMonitorHistories(map[string]any{"server_id": id})
monitorInfos, _ = utils.Json.Marshal(monitorHistories)
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
var isViewPasswordVerfied bool
if err := singleton.DB.Model(&model.MonitorHistory{}).
Select("distinct(server_id)").
@ -174,7 +174,7 @@ func (cp *commonPage) network(c *gin.Context) {
func (cp *commonPage) getServerStat(c *gin.Context, withPublicNote bool) ([]byte, error) {
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
_, isViewPasswordVerfied := c.Get(model.CtxKeyViewPasswordVerified)
var isViewPasswordVerfied bool
authorized := isMember || isViewPasswordVerfied
v, err, _ := cp.requestGroup.Do(fmt.Sprintf("serverStats::%t", authorized), func() (interface{}, error) {
singleton.SortedServerLock.RLock()

View File

@ -22,25 +22,6 @@ import (
"github.com/naiba/nezha/service/singleton"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func ServeWeb() *http.Server {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
@ -53,25 +34,14 @@ func ServeWeb() *http.Server {
if singleton.Conf.Debug {
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
}
r.Use(recordPath)
routers(r)
page404 := func(c *gin.Context) {
// mygin.ShowErrorPage(c, mygin.ErrInfo{
// Code: http.StatusNotFound,
// Title: "该页面不存在",
// Msg: "该页面内容可能已着陆火星",
// Link: "/",
// Btn: "返回首页",
// }, true)
}
r.NoRoute(page404)
r.NoMethod(page404)
srv := &http.Server{
return &http.Server{
ReadHeaderTimeout: time.Second * 5,
Handler: r,
}
return srv
}
func routers(r *gin.Engine) {
@ -79,27 +49,26 @@ func routers(r *gin.Engine) {
if err != nil {
log.Fatal("JWT Error:" + err.Error())
}
api := r.Group("api/v1")
api.Use(handlerMiddleWare(authMiddleware))
// register middleware
r.Use(handlerMiddleWare(authMiddleware))
api.POST("/login", authMiddleware.LoginHandler)
r.POST("/login", authMiddleware.LoginHandler)
auth := r.Group("/auth", authMiddleware.MiddlewareFunc())
auth := api.Group("", authMiddleware.MiddlewareFunc())
auth.GET("/refresh_token", authMiddleware.RefreshHandler)
// 通用页面
cp := commonPage{r: r}
cp.serve()
// 会员页面
mp := &memberPage{r}
mp.serve()
// API
api := r.Group("api")
{
ma := &memberAPI{api}
ma.serve()
}
// cp := commonPage{r: r}
// cp.serve()
// // 会员页面
// mp := &memberPage{r}
// mp.serve()
// // API
// external := api.Group("api")
// {
// ma := &memberAPI{external}
// ma.serve()
// }
}
func natGateway(c *gin.Context) {

View File

@ -1,7 +1,9 @@
package controller
import (
"fmt"
"log"
"net/http"
"time"
jwt "github.com/appleboy/gin-jwt/v2"
@ -15,6 +17,7 @@ func initParams() *jwt.GinJWTMiddleware {
return &jwt.GinJWTMiddleware{
Realm: singleton.Conf.SiteName,
Key: []byte(singleton.Conf.SecretKey),
CookieName: "nz-jwt",
Timeout: time.Hour,
MaxRefresh: time.Hour,
IdentityKey: model.CtxKeyAuthorizedUser,
@ -24,9 +27,20 @@ func initParams() *jwt.GinJWTMiddleware {
Authenticator: authenticator(),
Authorizator: authorizator(),
Unauthorized: unauthorized(),
TokenLookup: "header: Authorization, query: token, cookie: jwt",
TokenLookup: "header: Authorization, query: token, cookie: nz-jwt",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
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,
}
}
@ -41,9 +55,9 @@ func handlerMiddleWare(authMiddleware *jwt.GinJWTMiddleware) gin.HandlerFunc {
func payloadFunc() func(data interface{}) jwt.MapClaims {
return func(data interface{}) jwt.MapClaims {
if v, ok := data.(*model.User); ok {
if v, ok := data.(string); ok {
return jwt.MapClaims{
model.CtxKeyAuthorizedUser: v.ID,
model.CtxKeyAuthorizedUser: v,
}
}
return jwt.MapClaims{}
@ -53,7 +67,7 @@ func payloadFunc() func(data interface{}) jwt.MapClaims {
func identityHandler() func(c *gin.Context) interface{} {
return func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
userId := claims[model.CtxKeyAuthorizedUser].(uint64)
userId := claims[model.CtxKeyAuthorizedUser].(string)
var user model.User
if err := singleton.DB.First(&user, userId).Error; err != nil {
return nil
@ -62,15 +76,15 @@ func identityHandler() func(c *gin.Context) interface{} {
}
}
// login test godoc
// @Summary ping example
// User Login
// @Summary user login
// @Schemes
// @Description do ping
// @Tags example
// @Description user login
// @Accept json
// @param request body model.LoginRequest true "Login Request"
// @Produce json
// @Success 200 {string} Helloworld
// @Router /example/login [get]
// @Success 200 {object} model.CommonResponse[model.LoginResponse]
// @Router /login [post]
func authenticator() func(c *gin.Context) (interface{}, error) {
return func(c *gin.Context) (interface{}, error) {
var loginVals model.LoginRequest
@ -79,7 +93,7 @@ func authenticator() func(c *gin.Context) (interface{}, error) {
}
var user model.User
if err := singleton.DB.Select("id").Where("username = ?", loginVals.Username).First(&user).Error; err != nil {
if err := singleton.DB.Select("id", "password").Where("username = ?", loginVals.Username).First(&user).Error; err != nil {
return nil, jwt.ErrFailedAuthentication
}
@ -87,7 +101,11 @@ func authenticator() func(c *gin.Context) (interface{}, error) {
return nil, jwt.ErrFailedAuthentication
}
return &user, nil
if err := singleton.DB.Model(&user).Update("login_expire", time.Now().Add(time.Hour)).Error; err != nil {
return nil, jwt.ErrFailedAuthentication
}
return fmt.Sprintf("%d", user.ID), nil
}
}
@ -100,11 +118,37 @@ func authorizator() func(data interface{}, c *gin.Context) bool {
func unauthorized() func(c *gin.Context, code int, message string) {
return func(c *gin.Context, code int, message string) {
c.JSON(code, model.CommonResponse{
c.JSON(http.StatusOK, model.CommonResponse[interface{}]{
Success: false,
Error: model.CommonError{
Code: model.ApiErrorUnauthorized,
},
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{}]{
Success: false,
Error: "ApiErrorUnauthorized",
})
return
}
c.JSON(http.StatusOK, model.CommonResponse[model.LoginResponse]{
Success: true,
Data: model.LoginResponse{
Token: token,
Expire: expire.Format(time.RFC3339),
},
})
}

View File

@ -78,6 +78,27 @@ func initSystem() {
}
}
// @title Nezha Monitoring API
// @version 1.0
// @description Nezha Monitoring API
// @termsOfService http://nezhahq.github.io
// @contact.name API Support
// @contact.url http://nezhahq.github.io
// @contact.email hi@nai.ba
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8008
// @BasePath /api/v1
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
if dashboardCliParam.Version {
fmt.Println(singleton.Version)
@ -103,17 +124,23 @@ func main() {
srv := controller.ServeWeb()
go dispatchReportInfoTask()
if err := graceful.Graceful(func() error {
return srv.Serve(httpL)
}, func(c context.Context) error {
log.Println("NEZHA>> Graceful::START")
singleton.RecordTransferHourlyUsage()
log.Println("NEZHA>> Graceful::END")
m.Close()
return nil
}); err != nil {
log.Printf("NEZHA>> ERROR: %v", err)
}
go func() {
if err := graceful.Graceful(func() error {
log.Println("NEZHA>> Dashboard::START", singleton.Conf.ListenPort)
return srv.Serve(httpL)
}, func(c context.Context) error {
log.Println("NEZHA>> Graceful::START")
singleton.RecordTransferHourlyUsage()
log.Println("NEZHA>> Graceful::END")
m.Close()
return nil
}); err != nil {
log.Printf("NEZHA>> ERROR: %v", err)
}
}()
m.Serve()
}
func dispatchReportInfoTask() {

View File

@ -23,17 +23,17 @@ func (r ServiceItemResponse) TotalUptime() float32 {
}
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type CommonError struct {
Code int `json:"code"`
Args map[string]string `json:"args"`
type CommonResponse[T any] struct {
Success bool `json:"success,omitempty"`
Data T `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
type CommonResponse struct {
Success bool `json:"success"`
Data interface{} `json:"data"`
Error CommonError `json:"error"`
type LoginResponse struct {
Token string `json:"token,omitempty"`
Expire string `json:"expire,omitempty"`
}

View File

@ -7,15 +7,12 @@ import (
)
const CtxKeyAuthorizedUser = "ckau"
const CtxKeyViewPasswordVerified = "ckvpv"
const CtxKeyPreferredTheme = "ckpt"
const CacheKeyOauth2State = "p:a:state"
type Common struct {
ID uint64 `gorm:"primaryKey"`
CreatedAt time.Time `gorm:"index;<-:create"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
ID uint64 `gorm:"primaryKey" json:"id,omitempty"`
CreatedAt time.Time `gorm:"index;<-:create" json:"created_at,omitempty"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at,omitempty"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
}
type Response struct {

View File

@ -117,6 +117,9 @@ func (c *Config) Read(path string) error {
return err
}
if c.ListenPort == 0 {
c.ListenPort = 8008
}
if c.Language == "" {
c.Language = "zh-CN"
}

View File

@ -2,6 +2,6 @@ package model
type User struct {
Common
Username string
Password string
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty" gorm:"type:char(72)"`
}

View File

@ -1,4 +1,4 @@
swag init -g ./cmd/dashboard/main.go -o ./cmd/dashboard/docs
swag init --pd -d . -g ./cmd/dashboard/main.go -o ./cmd/dashboard/docs
protoc --go-grpc_out="require_unimplemented_servers=false:." --go_out="." proto/*.proto
rm -rf ../agent/proto
cp -r proto ../agent