实现Ticket登录

This commit is contained in:
GoEdgeLab
2024-05-10 14:28:36 +08:00
parent 349fa80f2d
commit cdb1f049f3
7 changed files with 286 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
package models
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"math/rand"
"time"
)
func init() {
if !teaconst.IsMain {
return
}
// 清理过期的票据
var ticker = time.NewTicker(time.Duration(rands.Int(36, 48)) * time.Hour)
goman.New(func() {
for range ticker.C {
err := SharedLoginTicketDAO.CleanExpiredTickets(nil)
if err != nil {
remotelogs.Error("LoginTicketDAO", "clean expired tickets failed: "+err.Error())
}
}
})
}
type LoginTicketDAO dbs.DAO
func NewLoginTicketDAO() *LoginTicketDAO {
return dbs.NewDAO(&LoginTicketDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeLoginTickets",
Model: new(LoginTicket),
PkName: "id",
},
}).(*LoginTicketDAO)
}
var SharedLoginTicketDAO *LoginTicketDAO
func init() {
dbs.OnReady(func() {
SharedLoginTicketDAO = NewLoginTicketDAO()
})
}
// CreateLoginTicket 创建票据
func (this *LoginTicketDAO) CreateLoginTicket(tx *dbs.Tx, adminId int64, userId int64, ip string) (ticketValue string, err error) {
if adminId <= 0 && userId <= 0 {
err = errors.New("either 'adminId' or 'userId' must be greater than 0")
return
}
if len(ip) > 0 && !iputils.IsValid(ip) {
err = errors.New("invalid ip: '" + ip + "'")
return
}
ticketValue = stringutil.Md5(types.String(adminId) + "@" + types.String(userId) + types.String(time.Now().UnixNano()) + "@" + types.String(rand.Int63()) + "@" + ip)
var op = NewLoginTicketOperator()
op.AdminId = adminId
op.UserId = userId
op.ExpiresAt = time.Now().Unix() + 600 /* 10 minutes */
op.Ip = ip
op.Value = ticketValue
err = this.Save(tx, op)
if err != nil {
return
}
return ticketValue, nil
}
// FindLoginTicketWithValue 查找票据
func (this *LoginTicketDAO) FindLoginTicketWithValue(tx *dbs.Tx, value string) (*LoginTicket, error) {
if len(value) == 0 {
return nil, nil
}
if len(value) != 32 {
return nil, nil
}
one, err := this.Query(tx).
Attr("value", value).
Gt("expiresAt", time.Now().Unix()).
Find()
if one == nil || err != nil {
return nil, err
}
var ticket = one.(*LoginTicket)
// delete the ticket
err = this.Query(tx).
Pk(ticket.Id).
DeleteQuickly()
if err != nil {
return nil, err
}
return ticket, nil
}
// CleanExpiredTickets 清理过期的票据
func (this *LoginTicketDAO) CleanExpiredTickets(tx *dbs.Tx) error {
return this.Query(tx).
Lt("expiresAt", time.Now().Unix()).
DeleteQuickly()
}

View File

@@ -0,0 +1,6 @@
package models_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,35 @@
package models
import "github.com/iwind/TeaGo/dbs"
const (
LoginTicketField_Id dbs.FieldName = "id" // ID
LoginTicketField_ExpiresAt dbs.FieldName = "expiresAt" // 过期时间
LoginTicketField_Value dbs.FieldName = "value" // 票据值
LoginTicketField_AdminId dbs.FieldName = "adminId" // 管理员ID
LoginTicketField_UserId dbs.FieldName = "userId" // 用户ID
LoginTicketField_Ip dbs.FieldName = "ip" // 用户IP
)
// LoginTicket 登录票据
type LoginTicket struct {
Id uint64 `field:"id"` // ID
ExpiresAt uint64 `field:"expiresAt"` // 过期时间
Value string `field:"value"` // 票据值
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
Ip string `field:"ip"` // 用户IP
}
type LoginTicketOperator struct {
Id any // ID
ExpiresAt any // 过期时间
Value any // 票据值
AdminId any // 管理员ID
UserId any // 用户ID
Ip any // 用户IP
}
func NewLoginTicketOperator() *LoginTicketOperator {
return &LoginTicketOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -334,6 +334,11 @@ func (this *APINode) registerServices(server *grpc.Server) {
pb.RegisterLoginSessionServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&services.LoginTicketService{}).(*services.LoginTicketService)
pb.RegisterLoginTicketServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&services.UserAccessKeyService{}).(*services.UserAccessKeyService)
pb.RegisterUserAccessKeyServiceServer(server, instance)

View File

@@ -0,0 +1,71 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package services
import (
"context"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// LoginTicketService 登录票据相关服务
type LoginTicketService struct {
BaseService
}
// CreateLoginTicket 创建票据
func (this *LoginTicketService) CreateLoginTicket(ctx context.Context, req *pb.CreateLoginTicketRequest) (*pb.CreateLoginTicketResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if req.AdminId <= 0 && req.UserId <= 0 {
return nil, errors.New("either 'adminId' or 'userId' must be greater than 0")
}
if len(req.Ip) > 0 && !iputils.IsValid(req.Ip) {
return nil, errors.New("invalid ip: '" + req.Ip + "'")
}
var tx = this.NullTx()
value, err := models.SharedLoginTicketDAO.CreateLoginTicket(tx, req.AdminId, req.UserId, req.Ip)
if err != nil {
return nil, err
}
return &pb.CreateLoginTicketResponse{Value: value}, nil
}
// FindLoginTicketWithValue 查找票据
// 查找成功后,会自动删除票据信息,所以票据信息只能查询一次
func (this *LoginTicketService) FindLoginTicketWithValue(ctx context.Context, req *pb.FindLoginTicketWithValueRequest) (*pb.FindLoginTicketWithValueResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, false)
if err != nil {
return nil, err
}
var tx = this.NullTx()
ticket, err := models.SharedLoginTicketDAO.FindLoginTicketWithValue(tx, req.Value)
if err != nil {
return nil, err
}
if ticket == nil {
return &pb.FindLoginTicketWithValueResponse{
LoginTicket: nil,
}, nil
}
return &pb.FindLoginTicketWithValueResponse{
LoginTicket: &pb.LoginTicket{
Id: int64(ticket.Id),
ExpiresAt: int64(ticket.ExpiresAt),
Value: ticket.Value,
AdminId: int64(ticket.AdminId),
UserId: int64(ticket.UserId),
Ip: ticket.Ip,
},
}, nil
}

View File

@@ -115193,6 +115193,53 @@
],
"records": []
},
{
"name": "edgeLoginTickets",
"engine": "InnoDB",
"charset": "utf8mb4_general_ci",
"definition": "CREATE TABLE `edgeLoginTickets` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n `expiresAt` bigint(11) unsigned DEFAULT '0' COMMENT '过期时间',\n `value` varchar(32) DEFAULT NULL COMMENT '票据值',\n `adminId` int(11) unsigned DEFAULT '0' COMMENT '管理员ID',\n `userId` int(11) unsigned DEFAULT '0' COMMENT '用户ID',\n `ip` varchar(64) DEFAULT NULL COMMENT '用户IP',\n PRIMARY KEY (`id`),\n KEY `value` (`value`),\n KEY `expiresAt` (`expiresAt`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登录票据'",
"fields": [
{
"name": "id",
"definition": "bigint(20) unsigned auto_increment COMMENT 'ID'"
},
{
"name": "expiresAt",
"definition": "bigint(11) unsigned DEFAULT '0' COMMENT '过期时间'"
},
{
"name": "value",
"definition": "varchar(32) COMMENT '票据值'"
},
{
"name": "adminId",
"definition": "int(11) unsigned DEFAULT '0' COMMENT '管理员ID'"
},
{
"name": "userId",
"definition": "int(11) unsigned DEFAULT '0' COMMENT '用户ID'"
},
{
"name": "ip",
"definition": "varchar(64) COMMENT '用户IP'"
}
],
"indexes": [
{
"name": "PRIMARY",
"definition": "UNIQUE KEY `PRIMARY` (`id`) USING BTREE"
},
{
"name": "value",
"definition": "KEY `value` (`value`) USING BTREE"
},
{
"name": "expiresAt",
"definition": "KEY `expiresAt` (`expiresAt`) USING BTREE"
}
],
"records": []
},
{
"name": "edgeLogins",
"engine": "InnoDB",