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.DELETE("/:model/:id", ma.delete)
|
||||
mr.POST("/logout", ma.logout)
|
||||
mr.GET("/token", ma.getToken)
|
||||
mr.POST("/token", ma.issueNewToken)
|
||||
mr.DELETE("/token/:token", ma.deleteToken)
|
||||
|
||||
// API
|
||||
mr.GET("/server/list", ma.serverList)
|
||||
mr.GET("/server/details", ma.serverDetails)
|
||||
v1 := ma.r.Group("v1")
|
||||
{
|
||||
apiv1 := &apiV1{v1}
|
||||
apiv1.serve()
|
||||
}
|
||||
}
|
||||
|
||||
// serverList 获取服务器列表 不传入Query参数则获取全部
|
||||
// header: Authorization: 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())
|
||||
type apiResult struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// serverDetails 获取服务器信息 不传入Query参数则获取全部
|
||||
// header: Authorization: Token
|
||||
// query: idList (服务器ID,逗号分隔,优先级高于tag查询)
|
||||
// query: tag (服务器分组)
|
||||
func (ma *memberAPI) 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
|
||||
// getToken 获取 Token
|
||||
func (ma *memberAPI) getToken(c *gin.Context) {
|
||||
u := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
|
||||
tokenList := singleton.UserIDToApiTokenList[u.ID]
|
||||
res := make([]*apiResult, len(tokenList))
|
||||
for i, token := range tokenList {
|
||||
res[i] = &apiResult{
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
tag := c.Query("tag")
|
||||
serverAPI := &singleton.ServerAPI{
|
||||
Token: token,
|
||||
IDList: idList,
|
||||
Tag: tag,
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"result": res,
|
||||
})
|
||||
}
|
||||
|
||||
// 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 != "" {
|
||||
c.JSON(200, serverAPI.GetStatusByTag())
|
||||
singleton.DB.Create(token)
|
||||
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
|
||||
}
|
||||
if len(idList) != 0 {
|
||||
c.JSON(200, serverAPI.GetStatusByIDList())
|
||||
if _, ok := singleton.ApiTokenList[token]; !ok {
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: "token 不存在",
|
||||
})
|
||||
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) {
|
||||
|
@ -1,10 +1,7 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type ApiToken struct {
|
||||
Common
|
||||
UserId uint64 `json:"user_id"`
|
||||
Token string `json:"token"`
|
||||
TokenExpired time.Time `json:"token_expired"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ type AuthorizeOption struct {
|
||||
Guest bool
|
||||
Member bool
|
||||
IsPage bool
|
||||
AllowAPI bool
|
||||
Msg string
|
||||
Redirect string
|
||||
Btn string
|
||||
@ -50,18 +51,20 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
|
||||
}
|
||||
|
||||
// API鉴权
|
||||
apiToken := c.GetHeader("Authorization")
|
||||
if apiToken != "" {
|
||||
var t model.ApiToken
|
||||
// TODO: 需要有缓存机制 减少数据库查询次数
|
||||
if err := singleton.DB.Where("token = ?", apiToken).First(&t).Error; err == nil {
|
||||
isLogin = t.TokenExpired.After(time.Now())
|
||||
}
|
||||
if isLogin {
|
||||
c.Set(model.CtxKeyAuthorizedUser, &t)
|
||||
if opt.AllowAPI {
|
||||
apiToken := c.GetHeader("Authorization")
|
||||
if apiToken != "" {
|
||||
var u model.User
|
||||
if _, ok := singleton.ApiTokenList[apiToken]; ok {
|
||||
err := singleton.DB.First(&u).Where("id = ?", singleton.ApiTokenList[apiToken].UserID).Error
|
||||
isLogin = err == nil
|
||||
}
|
||||
if isLogin {
|
||||
c.Set(model.CtxKeyAuthorizedUser, &u)
|
||||
c.Set("isAPI", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 已登录且只能游客访问
|
||||
if isLogin && opt.Guest {
|
||||
ShowErrorPage(c, commonErr, opt.IsPage)
|
||||
|
@ -5,6 +5,11 @@ import (
|
||||
"github.com/naiba/nezha/pkg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
ApiTokenList = make(map[string]*model.ApiToken)
|
||||
UserIDToApiTokenList = make(map[uint64][]string)
|
||||
)
|
||||
|
||||
type ServerAPI struct {
|
||||
Token string // 传入Token 后期可能会需要用于scope判定
|
||||
IDList []uint64
|
||||
@ -45,6 +50,21 @@ type ServerInfoResponse struct {
|
||||
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的服务器状态信息
|
||||
func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse {
|
||||
res := &ServerStatusResponse{}
|
||||
|
@ -38,6 +38,7 @@ func LoadSingleton() {
|
||||
LoadNotifications() // 加载通知服务
|
||||
LoadServers() // 加载服务器列表
|
||||
LoadCronTasks() // 加载定时任务
|
||||
LoadAPI()
|
||||
}
|
||||
|
||||
// InitConfigFromPath 从给出的文件路径中加载配置
|
||||
|
Loading…
Reference in New Issue
Block a user