mirror of
https://github.com/nezhahq/nezha.git
synced 2025-02-02 01:28:13 -05:00
feat: Token生成|存储|验证
This commit is contained in:
parent
5d356a30e2
commit
990394bf46
78
cmd/dashboard/controller/api_v1.go
Normal file
78
cmd/dashboard/controller/api_v1.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/naiba/nezha/pkg/mygin"
|
||||||
|
"github.com/naiba/nezha/service/singleton"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type apiV1 struct {
|
||||||
|
r gin.IRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *apiV1) serve() {
|
||||||
|
r := v.r.Group("")
|
||||||
|
// API
|
||||||
|
r.Use(mygin.Authorize(mygin.AuthorizeOption{
|
||||||
|
Member: true,
|
||||||
|
IsPage: false,
|
||||||
|
AllowAPI: true,
|
||||||
|
Msg: "访问此接口需要认证",
|
||||||
|
Btn: "点此登录",
|
||||||
|
Redirect: "/login",
|
||||||
|
}))
|
||||||
|
r.GET("/server/list", v.serverList)
|
||||||
|
r.GET("/server/details", v.serverDetails)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverList 获取服务器列表 不传入Query参数则获取全部
|
||||||
|
// header: Authorization: Token
|
||||||
|
// query: tag (服务器分组)
|
||||||
|
func (v *apiV1) serverList(c *gin.Context) {
|
||||||
|
token, _ := c.Cookie("Authorization")
|
||||||
|
tag := c.Query("tag")
|
||||||
|
serverAPI := &singleton.ServerAPI{
|
||||||
|
Token: token,
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
if tag != "" {
|
||||||
|
c.JSON(200, serverAPI.GetListByTag())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, serverAPI.GetAllList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverDetails 获取服务器信息 不传入Query参数则获取全部
|
||||||
|
// header: Authorization: Token
|
||||||
|
// query: idList (服务器ID,逗号分隔,优先级高于tag查询)
|
||||||
|
// query: tag (服务器分组)
|
||||||
|
func (v *apiV1) serverDetails(c *gin.Context) {
|
||||||
|
token, _ := c.Cookie("Authorization")
|
||||||
|
var idList []uint64
|
||||||
|
idListStr := strings.Split(c.Query("id"), ",")
|
||||||
|
if c.Query("id") != "" {
|
||||||
|
idList = make([]uint64, len(idListStr))
|
||||||
|
for i, v := range idListStr {
|
||||||
|
id, _ := strconv.ParseUint(v, 10, 64)
|
||||||
|
idList[i] = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag := c.Query("tag")
|
||||||
|
serverAPI := &singleton.ServerAPI{
|
||||||
|
Token: token,
|
||||||
|
IDList: idList,
|
||||||
|
Tag: tag,
|
||||||
|
}
|
||||||
|
if tag != "" {
|
||||||
|
c.JSON(200, serverAPI.GetStatusByTag())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(idList) != 0 {
|
||||||
|
c.JSON(200, serverAPI.GetStatusByIDList())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, serverAPI.GetAllStatus())
|
||||||
|
}
|
@ -43,59 +43,93 @@ func (ma *memberAPI) serve() {
|
|||||||
mr.POST("/setting", ma.updateSetting)
|
mr.POST("/setting", ma.updateSetting)
|
||||||
mr.DELETE("/:model/:id", ma.delete)
|
mr.DELETE("/:model/:id", ma.delete)
|
||||||
mr.POST("/logout", ma.logout)
|
mr.POST("/logout", ma.logout)
|
||||||
|
mr.GET("/token", ma.getToken)
|
||||||
|
mr.POST("/token", ma.issueNewToken)
|
||||||
|
mr.DELETE("/token/:token", ma.deleteToken)
|
||||||
|
|
||||||
// API
|
// API
|
||||||
mr.GET("/server/list", ma.serverList)
|
v1 := ma.r.Group("v1")
|
||||||
mr.GET("/server/details", ma.serverDetails)
|
{
|
||||||
|
apiv1 := &apiV1{v1}
|
||||||
|
apiv1.serve()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverList 获取服务器列表 不传入Query参数则获取全部
|
type apiResult struct {
|
||||||
// header: Authorization: Token
|
Token string `json:"token"`
|
||||||
// query: tag (服务器分组)
|
|
||||||
func (ma *memberAPI) serverList(c *gin.Context) {
|
|
||||||
token, _ := c.Cookie("Authorization")
|
|
||||||
tag := c.Query("tag")
|
|
||||||
serverAPI := &singleton.ServerAPI{
|
|
||||||
Token: token,
|
|
||||||
Tag: tag,
|
|
||||||
}
|
|
||||||
if tag != "" {
|
|
||||||
c.JSON(200, serverAPI.GetListByTag())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(200, serverAPI.GetAllList())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverDetails 获取服务器信息 不传入Query参数则获取全部
|
// getToken 获取 Token
|
||||||
// header: Authorization: Token
|
func (ma *memberAPI) getToken(c *gin.Context) {
|
||||||
// query: idList (服务器ID,逗号分隔,优先级高于tag查询)
|
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
|
||||||
// query: tag (服务器分组)
|
tokenList := singleton.UserIDToApiTokenList[u.ID]
|
||||||
func (ma *memberAPI) serverDetails(c *gin.Context) {
|
res := make([]*apiResult, len(tokenList))
|
||||||
token, _ := c.Cookie("Authorization")
|
for i, token := range tokenList {
|
||||||
var idList []uint64
|
res[i] = &apiResult{
|
||||||
idListStr := strings.Split(c.Query("id"), ",")
|
Token: token,
|
||||||
if c.Query("id") != "" {
|
|
||||||
idList = make([]uint64, len(idListStr))
|
|
||||||
for i, v := range idListStr {
|
|
||||||
id, _ := strconv.ParseUint(v, 10, 64)
|
|
||||||
idList[i] = id
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tag := c.Query("tag")
|
c.JSON(http.StatusOK, gin.H{
|
||||||
serverAPI := &singleton.ServerAPI{
|
"code": 0,
|
||||||
Token: token,
|
"message": "success",
|
||||||
IDList: idList,
|
"result": res,
|
||||||
Tag: tag,
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// issueNewToken 生成新的 token
|
||||||
|
func (ma *memberAPI) issueNewToken(c *gin.Context) {
|
||||||
|
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
|
||||||
|
token := &model.ApiToken{
|
||||||
|
UserID: u.ID,
|
||||||
|
Token: utils.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login)),
|
||||||
}
|
}
|
||||||
if tag != "" {
|
singleton.DB.Create(token)
|
||||||
c.JSON(200, serverAPI.GetStatusByTag())
|
singleton.ApiTokenList[token.Token] = token
|
||||||
|
singleton.UserIDToApiTokenList[u.ID] = append(singleton.UserIDToApiTokenList[u.ID], token.Token)
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
Message: "success",
|
||||||
|
Result: map[string]string{
|
||||||
|
"token": token.Token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteToken 删除 token
|
||||||
|
func (ma *memberAPI) deleteToken(c *gin.Context) {
|
||||||
|
token := c.Param("token")
|
||||||
|
if token == "" {
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "token 不能为空",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(idList) != 0 {
|
if _, ok := singleton.ApiTokenList[token]; !ok {
|
||||||
c.JSON(200, serverAPI.GetStatusByIDList())
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: "token 不存在",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(200, serverAPI.GetAllStatus())
|
// 在数据库中删除该Token
|
||||||
|
singleton.DB.Unscoped().Delete(&model.ApiToken{}, "token = ?", token)
|
||||||
|
// 在UserIDToApiTokenList中删除该Token
|
||||||
|
for i, t := range singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] {
|
||||||
|
if t == token {
|
||||||
|
singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID] = append(singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID][:i], singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID][i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(singleton.UserIDToApiTokenList[singleton.ApiTokenList[token].UserID]) == 0 {
|
||||||
|
delete(singleton.UserIDToApiTokenList, singleton.ApiTokenList[token].UserID)
|
||||||
|
}
|
||||||
|
// 在ApiTokenList中删除该Token
|
||||||
|
delete(singleton.ApiTokenList, token)
|
||||||
|
c.JSON(http.StatusOK, model.Response{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
Message: "success",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ma *memberAPI) delete(c *gin.Context) {
|
func (ma *memberAPI) delete(c *gin.Context) {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type ApiToken struct {
|
type ApiToken struct {
|
||||||
Common
|
Common
|
||||||
UserId uint64 `json:"user_id"`
|
UserID uint64 `json:"user_id"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
TokenExpired time.Time `json:"token_expired"`
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ type AuthorizeOption struct {
|
|||||||
Guest bool
|
Guest bool
|
||||||
Member bool
|
Member bool
|
||||||
IsPage bool
|
IsPage bool
|
||||||
|
AllowAPI bool
|
||||||
Msg string
|
Msg string
|
||||||
Redirect string
|
Redirect string
|
||||||
Btn string
|
Btn string
|
||||||
@ -50,18 +51,20 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// API鉴权
|
// API鉴权
|
||||||
apiToken := c.GetHeader("Authorization")
|
if opt.AllowAPI {
|
||||||
if apiToken != "" {
|
apiToken := c.GetHeader("Authorization")
|
||||||
var t model.ApiToken
|
if apiToken != "" {
|
||||||
// TODO: 需要有缓存机制 减少数据库查询次数
|
var u model.User
|
||||||
if err := singleton.DB.Where("token = ?", apiToken).First(&t).Error; err == nil {
|
if _, ok := singleton.ApiTokenList[apiToken]; ok {
|
||||||
isLogin = t.TokenExpired.After(time.Now())
|
err := singleton.DB.First(&u).Where("id = ?", singleton.ApiTokenList[apiToken].UserID).Error
|
||||||
}
|
isLogin = err == nil
|
||||||
if isLogin {
|
}
|
||||||
c.Set(model.CtxKeyAuthorizedUser, &t)
|
if isLogin {
|
||||||
|
c.Set(model.CtxKeyAuthorizedUser, &u)
|
||||||
|
c.Set("isAPI", true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 已登录且只能游客访问
|
// 已登录且只能游客访问
|
||||||
if isLogin && opt.Guest {
|
if isLogin && opt.Guest {
|
||||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
ShowErrorPage(c, commonErr, opt.IsPage)
|
||||||
|
@ -5,6 +5,11 @@ import (
|
|||||||
"github.com/naiba/nezha/pkg/utils"
|
"github.com/naiba/nezha/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ApiTokenList = make(map[string]*model.ApiToken)
|
||||||
|
UserIDToApiTokenList = make(map[uint64][]string)
|
||||||
|
)
|
||||||
|
|
||||||
type ServerAPI struct {
|
type ServerAPI struct {
|
||||||
Token string // 传入Token 后期可能会需要用于scope判定
|
Token string // 传入Token 后期可能会需要用于scope判定
|
||||||
IDList []uint64
|
IDList []uint64
|
||||||
@ -45,6 +50,21 @@ type ServerInfoResponse struct {
|
|||||||
Result []*CommonServerInfo `json:"result"`
|
Result []*CommonServerInfo `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitAPI() {
|
||||||
|
ApiTokenList = make(map[string]*model.ApiToken)
|
||||||
|
UserIDToApiTokenList = make(map[uint64][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadAPI() {
|
||||||
|
InitAPI()
|
||||||
|
var tokenList []*model.ApiToken
|
||||||
|
DB.Find(&tokenList)
|
||||||
|
for _, token := range tokenList {
|
||||||
|
ApiTokenList[token.Token] = token
|
||||||
|
UserIDToApiTokenList[token.UserID] = append(UserIDToApiTokenList[token.UserID], token.Token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetStatusByIDList 获取传入IDList的服务器状态信息
|
// GetStatusByIDList 获取传入IDList的服务器状态信息
|
||||||
func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse {
|
func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse {
|
||||||
res := &ServerStatusResponse{}
|
res := &ServerStatusResponse{}
|
||||||
|
@ -38,6 +38,7 @@ func LoadSingleton() {
|
|||||||
LoadNotifications() // 加载通知服务
|
LoadNotifications() // 加载通知服务
|
||||||
LoadServers() // 加载服务器列表
|
LoadServers() // 加载服务器列表
|
||||||
LoadCronTasks() // 加载定时任务
|
LoadCronTasks() // 加载定时任务
|
||||||
|
LoadAPI()
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitConfigFromPath 从给出的文件路径中加载配置
|
// InitConfigFromPath 从给出的文件路径中加载配置
|
||||||
|
Loading…
Reference in New Issue
Block a user