mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
refactor: login & refresh token
This commit is contained in:
parent
41391989e7
commit
53f1abb7c8
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -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() {
|
||||
|
18
model/api.go
18
model/api.go
@ -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"`
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)"`
|
||||
}
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user