登录与授权等
This commit is contained in:
		| @@ -5,12 +5,12 @@ import ( | ||||
| 	"mc-client-updater-server/internal/service" | ||||
| 	"mc-client-updater-server/pkg/param" | ||||
| 	"mc-client-updater-server/pkg/result" | ||||
| 	"mc-client-updater-server/pkg/util" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func HandleLogin(c *gin.Context) { | ||||
| 	srv := service.NewUserService(c) | ||||
| 	res := result.NewResult(c) | ||||
|  | ||||
| 	loginParam := param.LoginParam{} | ||||
| 	err := c.ShouldBindJSON(&loginParam) | ||||
| 	if err != nil { | ||||
| @@ -18,6 +18,62 @@ func HandleLogin(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	srv := service.NewUserService(c) | ||||
| 	srv.Login(loginParam.Username, loginParam.Password) | ||||
|  | ||||
| } | ||||
|  | ||||
| func HandleGrantAdd(c *gin.Context) { | ||||
| 	res := result.NewResult(c) | ||||
| 	p := param.NewGrantTokenParam{} | ||||
| 	err := c.ShouldBindJSON(&p) | ||||
| 	if err != nil { | ||||
| 		res.BadRequest() | ||||
| 		return | ||||
| 	} | ||||
| 	if p.ExpireAt != "" { | ||||
| 		isValid := util.IsSQLTimeFormat(p.ExpireAt) | ||||
| 		if !isValid { | ||||
| 			res.BadRequestWithMsg("请求参数错误:时间格式错误,应为2006-01-02 15:04:05格式") | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		if p.TTL == 0 { | ||||
| 			res.BadRequestWithMsg("请求参数错误:至少提供expire_at和ttl中的一项且不为0") | ||||
| 			return | ||||
| 		} | ||||
| 		p.ExpireAt = util.ToSQLTimeFormat(time.Now().Add(time.Duration(p.TTL) * time.Second)) | ||||
| 	} | ||||
|  | ||||
| 	srv := service.NewInstanceService(c) | ||||
| 	// 验证 target -> instance(name) 是否存在 | ||||
| 	_, err = srv.GetInstanceByName(p.Target) | ||||
| 	if err != nil { | ||||
| 		res.InvalidInstance(p.Target) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	grantEntity, err := srv.NewGrantToken(p.Target, p.ExpireAt) | ||||
| 	if err != nil { | ||||
| 		res.InternalServerError("生成授权码失败") | ||||
| 		return | ||||
| 	} | ||||
| 	res.Success(grantEntity) | ||||
| } | ||||
|  | ||||
| func HandleNewInstance(c *gin.Context) { | ||||
| 	res := result.NewResult(c) | ||||
| 	p := param.AddInstanceParam{} | ||||
| 	err := c.ShouldBindJSON(&p) | ||||
| 	if err != nil { | ||||
| 		res.BadRequest() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	srv := service.NewInstanceService(c) | ||||
| 	inst, err := srv.AddInstance(p.Name, p.UpdateURL) | ||||
| 	if err != nil { | ||||
| 		res.DuplicatedValue("实例名称已存在") | ||||
| 		return | ||||
| 	} | ||||
| 	res.Success(inst) | ||||
| } | ||||
|   | ||||
							
								
								
									
										5
									
								
								internal/api/v1/handler/instance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								internal/api/v1/handler/instance.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| package handler | ||||
|  | ||||
| func HandleInstanceUpdate() { | ||||
|  | ||||
| } | ||||
| @@ -11,7 +11,7 @@ func AdminRequired(c *gin.Context) { | ||||
| 	res := result.NewResult(c) | ||||
| 	authorization := c.GetHeader("Authorization") | ||||
| 	if authorization == "" { | ||||
| 		res.Unauthorized() | ||||
| 		res.UnLogin() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -2,10 +2,37 @@ package middleware | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"mc-client-updater-server/pkg/log" | ||||
| 	"gorm.io/gorm" | ||||
| 	"mc-client-updater-server/internal/service" | ||||
| 	"mc-client-updater-server/pkg/result" | ||||
| ) | ||||
|  | ||||
| func GrantRequired(c *gin.Context) { | ||||
| 	instName := c.Param("name") | ||||
| 	log.Logger.Info(instName) | ||||
| 	// 判断instance name是否存在 | ||||
| 	srv := service.NewInstanceService(c) | ||||
| 	res := result.NewResult(c) | ||||
| 	instEntity, err := srv.GetInstanceByName(instName) | ||||
| 	if err == gorm.ErrRecordNotFound { | ||||
| 		res.InvalidInstance(instName) | ||||
| 		return | ||||
| 	} else if err != nil { | ||||
| 		res.InternalServerError("查询实例对象时出现错误") | ||||
| 		return | ||||
| 	} | ||||
| 	c.Set("instance", instEntity) | ||||
|  | ||||
| 	// 判断grant_code是否合法 | ||||
| 	grantCode := c.GetHeader("GrantCode") | ||||
| 	if grantCode == "" { | ||||
| 		res.Unauthorized() | ||||
| 		return | ||||
| 	} | ||||
| 	grantEntity, err := srv.GetGrantByToken(grantCode) | ||||
| 	if err != nil { | ||||
| 		res.Unauthorized() | ||||
| 		return | ||||
| 	} | ||||
| 	c.Set("grant", grantEntity) | ||||
| 	c.Next() | ||||
| } | ||||
|   | ||||
| @@ -27,7 +27,7 @@ func NewRouter() *gin.Engine { | ||||
| 	*/ | ||||
| 	inst := v1.Group("/instance/:name", middleware.GrantRequired) | ||||
| 	{ | ||||
| 		inst.GET("/detail") | ||||
| 		inst.POST("/upload", ) | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -37,9 +37,8 @@ func NewRouter() *gin.Engine { | ||||
| 	*/ | ||||
| 	admin := v1.Group("/admin", middleware.AdminRequired) | ||||
| 	{ | ||||
| 		admin.GET("/instances") | ||||
| 		admin.GET("/users") | ||||
| 		admin.GET("/updates") | ||||
| 		admin.POST("/new_instance", handler.HandleNewInstance) | ||||
| 		admin.POST("/grant/add", handler.HandleGrantAdd) | ||||
| 	} | ||||
|  | ||||
| 	return r | ||||
|   | ||||
							
								
								
									
										54
									
								
								internal/service/instance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								internal/service/instance.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"mc-client-updater-server/pkg/dao" | ||||
| 	"mc-client-updater-server/pkg/dao/entity" | ||||
| 	"mc-client-updater-server/pkg/util" | ||||
| ) | ||||
|  | ||||
| type InstanceService struct { | ||||
| 	ctx *gin.Context | ||||
| } | ||||
|  | ||||
| func NewInstanceService(c *gin.Context) *InstanceService { | ||||
| 	return &InstanceService{ctx: c} | ||||
| } | ||||
|  | ||||
| func (s *InstanceService) AddInstance(name, updateURL string) (*entity.Instance, error) { | ||||
| 	instEntity := entity.Instance{ | ||||
| 		Name:      name, | ||||
| 		UpdateURL: updateURL, | ||||
| 	} | ||||
| 	tx := dao.DB().Create(&instEntity) | ||||
| 	if tx.Error != nil { | ||||
| 		return nil, tx.Error | ||||
| 	} | ||||
|  | ||||
| 	tx = dao.DB().Where(&instEntity).Last(&instEntity) | ||||
| 	return &instEntity, tx.Error | ||||
| } | ||||
|  | ||||
| func (s *InstanceService) GetInstanceByName(name string) (*entity.Instance, error) { | ||||
| 	instEntity := entity.Instance{} | ||||
| 	tx := dao.DB().Where("name=?", name).Last(&instEntity) | ||||
| 	return &instEntity, tx.Error | ||||
| } | ||||
|  | ||||
| func (s *InstanceService) NewGrantToken(instName string, expireStr string) (*entity.Grant, error) { | ||||
| 	expireAt := util.MustParseSQLTime(expireStr) | ||||
| 	grantEntity := entity.Grant{GrantTo: instName, ExpireAt: expireAt, Token: util.RandStr(32)} | ||||
| 	tx := dao.DB().Create(&grantEntity) | ||||
| 	if tx.Error != nil { | ||||
| 		return nil, tx.Error | ||||
| 	} | ||||
|  | ||||
| 	tx = dao.DB().Where(&grantEntity).Last(&grantEntity) | ||||
| 	return &grantEntity, tx.Error | ||||
| } | ||||
|  | ||||
| func (s *InstanceService) GetGrantByToken(token string) (*entity.Grant, error) { | ||||
| 	grantEntity := entity.Grant{} | ||||
| 	tx := dao.DB().Where("token=?", token).Last(&grantEntity) | ||||
| 	return &grantEntity, tx.Error | ||||
| } | ||||
| @@ -22,7 +22,7 @@ func (s *TokenService) VerifyToken(token string) (*entity.Token, bool) { | ||||
| 	// 是否存在 | ||||
| 	tokenRow := s.getToken(token) | ||||
| 	if tokenRow == nil { | ||||
| 		res.Unauthorized() | ||||
| 		res.UnLogin() | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	// 是否过期 | ||||
| @@ -34,8 +34,8 @@ func (s *TokenService) VerifyToken(token string) (*entity.Token, bool) { | ||||
| } | ||||
|  | ||||
| func (s *TokenService) getToken(token string) *entity.Token { | ||||
| 	tokenRow := entity.Token{Token: token} | ||||
| 	tx := dao.DB().Last(&tokenRow) | ||||
| 	tokenRow := entity.Token{} | ||||
| 	tx := dao.DB().Where("token=?", token).Last(&tokenRow) | ||||
| 	if tx.Error == gorm.ErrRecordNotFound { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -43,8 +43,8 @@ func (s *TokenService) getToken(token string) *entity.Token { | ||||
| } | ||||
|  | ||||
| func (s *TokenService) getTokenByUsername(username string) *entity.Token { | ||||
| 	tokenRow := entity.Token{GrantTo: username} | ||||
| 	tx := dao.DB().First(&tokenRow) | ||||
| 	tokenRow := entity.Token{} | ||||
| 	tx := dao.DB().Where("grant_to=?", username).Last(&tokenRow) | ||||
| 	if tx.Error == gorm.ErrRecordNotFound { | ||||
| 		return nil | ||||
| 	} | ||||
|   | ||||
| @@ -64,8 +64,8 @@ func (s *UserService) hasRole(role string, user *entity.User) bool { | ||||
| } | ||||
|  | ||||
| func (s *UserService) getUserByUsername(name string) *entity.User { | ||||
| 	user := entity.User{Username: name} | ||||
| 	tx := dao.DB().First(&user) | ||||
| 	user := entity.User{} | ||||
| 	tx := dao.DB().Where("username=?", name).First(&user) | ||||
| 	if tx.Error != nil { | ||||
| 		return nil | ||||
| 	} | ||||
|   | ||||
| @@ -1,7 +1,16 @@ | ||||
| package common | ||||
|  | ||||
| /* | ||||
| 2000 用户操作错误 | ||||
| 3000 预留 | ||||
| 4000 数据库操作错误 | ||||
| 5000 系统错误 | ||||
| */ | ||||
| const ( | ||||
| 	LoginErrorCode = 2001 | ||||
| 	NoPermission   = 2002 | ||||
| 	LoginExpired   = 2003 | ||||
| 	LoginErrorCode  = 2001 | ||||
| 	NoPermission    = 2002 | ||||
| 	LoginExpired    = 2003 | ||||
| 	InvalidInstance = 2004 | ||||
|  | ||||
| 	DuplicatedValue = 4001 | ||||
| ) | ||||
|   | ||||
| @@ -69,6 +69,7 @@ func migrate() { | ||||
| 	err = db.AutoMigrate(&entity.Update{}) | ||||
| 	err = db.AutoMigrate(&entity.Grant{}) | ||||
| 	err = db.AutoMigrate(&entity.Token{}) | ||||
| 	err = db.AutoMigrate(&entity.Metadata{}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Logger.Fatal("关联数据表失败:", err) | ||||
|   | ||||
| @@ -2,12 +2,13 @@ package entity | ||||
|  | ||||
| import ( | ||||
| 	"gorm.io/gorm" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Grant Grant是授权给实例的,给予实例访问权限 | ||||
| type Grant struct { | ||||
| 	gorm.Model `json:"model"` | ||||
| 	Token      string `gorm:"unique;not null" json:"token,omitempty"` | ||||
| 	TTL        int    `gorm:"not null;default:0" json:"ttl,omitempty"` | ||||
| 	GrantTo    uint   `gorm:"not null;default:0;comment:instances(id) 授权给实例,0表示无指定(所有)" json:"grant_to,omitempty"` | ||||
| 	Token      string    `gorm:"unique;not null" json:"token"` | ||||
| 	ExpireAt   time.Time `gorm:"index" json:"expire_at"` | ||||
| 	GrantTo    string    `gorm:"not null;default:'';comment:instances(name)授权给实例,空表示无指定(所有)" json:"grant_to"` | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,6 @@ import ( | ||||
|  | ||||
| type Instance struct { | ||||
| 	gorm.Model `json:"model"` | ||||
| 	Name       string `gorm:"unique;not null" json:"name,omitempty"` | ||||
| 	UpdateURL  string `gorm:"column:update_url;not null;default:'';comment:更新URL,未指定使用默认" json:"update_url,omitempty"` | ||||
| 	Name       string `gorm:"unique;not null" json:"name"` | ||||
| 	UpdateURL  string `gorm:"column:update_url;not null;default:'';comment:更新URL,未指定使用默认" json:"update_url"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										7
									
								
								pkg/dao/entity/metadata.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								pkg/dao/entity/metadata.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| package entity | ||||
|  | ||||
| type Metadata struct { | ||||
| 	ID    uint   `json:"id"` | ||||
| 	Key   string `gorm:"unique;not null" json:"key"` | ||||
| 	Value string `gorm:"not null;default:''" json:"value"` | ||||
| } | ||||
| @@ -5,7 +5,7 @@ import "gorm.io/gorm" | ||||
| // Token Token是授权给用户的,给予用户登录权限 | ||||
| type Token struct { | ||||
| 	gorm.Model `json:"model"` | ||||
| 	Token      string `gorm:"unique;not null" json:"token,omitempty"` | ||||
| 	GrantTo    string `gorm:"index;not null;default:''" json:"grant_to,omitempty"` | ||||
| 	TTL        int    `gorm:"not null;default:0" json:"ttl,omitempty"` | ||||
| 	Token      string `gorm:"unique;not null" json:"token"` | ||||
| 	GrantTo    string `gorm:"index;not null;default:''" json:"grant_to"` | ||||
| 	TTL        int    `gorm:"not null;default:0" json:"ttl"` | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import "gorm.io/gorm" | ||||
|  | ||||
| type Update struct { | ||||
| 	gorm.Model `json:"model"` | ||||
| 	HashID     string `gorm:"index;not null" json:"hash_id,omitempty"` | ||||
| 	Comment    string `gorm:"not null;default:'';comment:更新内容或注释" json:"comment,omitempty"` | ||||
| 	Changes    string `gorm:"not null;comment:更改的文件列表,逗号分隔,引用files(hash_id)" json:"changes,omitempty"` | ||||
| 	HashID     string `gorm:"index;not null" json:"hash_id"` | ||||
| 	Comment    string `gorm:"not null;default:'';comment:更新内容或注释" json:"comment"` | ||||
| 	Changes    string `gorm:"not null;comment:更改的文件列表,逗号分隔,引用files(hash_id)" json:"changes"` | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import "gorm.io/gorm" | ||||
|  | ||||
| type User struct { | ||||
| 	gorm.Model `json:"model"` | ||||
| 	Username   string `gorm:"unique;not null" json:"username,omitempty"` | ||||
| 	Password   string `gorm:"not null" json:"password,omitempty"` | ||||
| 	Roles      string `gorm:"not null;default:''" json:"roles,omitempty"` | ||||
| 	Username   string `gorm:"unique;not null" json:"username"` | ||||
| 	Password   string `gorm:"not null" json:"password"` | ||||
| 	Roles      string `gorm:"not null;default:''" json:"roles"` | ||||
| } | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| package param | ||||
|  | ||||
| type AuthorizeQueryParam struct { | ||||
| 	ClientId            uint   `form:"client_id" binding:"required"` | ||||
| 	ResponseType        string `form:"response_type" binding:"required"` | ||||
| 	State               string `form:"state" binding:"required"` | ||||
| 	Scope               string `form:"scope" binding:"required"` | ||||
| 	CodeChallenge       string `form:"code_challenge" binding:"required"` | ||||
| 	CodeChallengeMethod string `form:"code_challenge_method" binding:"required"` | ||||
| } | ||||
							
								
								
									
										12
									
								
								pkg/param/instance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								pkg/param/instance.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package param | ||||
|  | ||||
| type AddInstanceParam struct { | ||||
| 	Name      string `json:"name" binding:"required"` | ||||
| 	UpdateURL string `json:"update_url"` | ||||
| } | ||||
|  | ||||
| type NewGrantTokenParam struct { | ||||
| 	Target   string `json:"target" binding:"required"` | ||||
| 	TTL      int    `json:"ttl"` | ||||
| 	ExpireAt string `json:"expire_at"` | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| package param | ||||
|  | ||||
| type LoginParam struct { | ||||
| 	Username string `json:"username"` | ||||
| 	Password string `json:"password"` | ||||
| 	Username string `json:"username" binding:"required"` | ||||
| 	Password string `json:"password" binding:"required"` | ||||
| } | ||||
|   | ||||
| @@ -37,7 +37,7 @@ func (r *Result) Fail(code int, msg string) { | ||||
| 	res := Root{ | ||||
| 		Code: code, | ||||
| 		Msg:  msg, | ||||
| 		Data: gin.H{}, | ||||
| 		Data: nil, | ||||
| 	} | ||||
| 	r.ctx.JSON(http.StatusOK, res) | ||||
| 	r.ctx.Abort() | ||||
| @@ -57,14 +57,22 @@ func (r *Result) BadRequest() { | ||||
| 	r.Fail(http.StatusBadRequest, "请求参数错误") | ||||
| } | ||||
| 
 | ||||
| func (r *Result) BadRequestWithMsg(msg string) { | ||||
| 	r.Fail(http.StatusBadRequest, msg) | ||||
| } | ||||
| 
 | ||||
| func (r *Result) LoginError() { | ||||
| 	r.Fail(common.LoginErrorCode, "账号或密码错误") | ||||
| } | ||||
| 
 | ||||
| func (r *Result) Unauthorized() { | ||||
| func (r *Result) UnLogin() { | ||||
| 	r.Fail(http.StatusUnauthorized, "未登录") | ||||
| } | ||||
| 
 | ||||
| func (r *Result) Unauthorized() { | ||||
| 	r.Fail(http.StatusUnauthorized, "未授权") | ||||
| } | ||||
| 
 | ||||
| func (r *Result) NoPermission() { | ||||
| 	r.Fail(common.NoPermission, "权限不足") | ||||
| } | ||||
| @@ -72,3 +80,11 @@ func (r *Result) NoPermission() { | ||||
| func (r *Result) LoginExpired() { | ||||
| 	r.Fail(common.LoginExpired, "登录过期") | ||||
| } | ||||
| 
 | ||||
| func (r *Result) DuplicatedValue(msg string) { | ||||
| 	r.Fail(common.DuplicatedValue, msg) | ||||
| } | ||||
| 
 | ||||
| func (r *Result) InvalidInstance(instName string) { | ||||
| 	r.Fail(common.InvalidInstance, "指定的实例不存在:"+instName) | ||||
| } | ||||
							
								
								
									
										23
									
								
								pkg/util/time.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pkg/util/time.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| package util | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| func ToSQLTimeFormat(t time.Time) string { | ||||
| 	return t.Format("2006-01-02 15:04:05") | ||||
| } | ||||
|  | ||||
| func MustParseSQLTime(timeStr string) time.Time { | ||||
| 	timeObj, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return timeObj | ||||
| } | ||||
|  | ||||
| func IsSQLTimeFormat(timeStr string) bool { | ||||
| 	_, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| package util | ||||
|  | ||||
| func GenSessionID() string { | ||||
| 	return RandStr(32) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user