feat: gateway
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing

This commit is contained in:
vaalacat
2024-04-23 11:18:16 +00:00
parent a42fecfc63
commit 75944abbc2
13 changed files with 404 additions and 87 deletions

86
services/gateway/auth.go Normal file
View File

@@ -0,0 +1,86 @@
package gateway
import (
"fmt"
"sync"
"tg-mc/conf"
"tg-mc/defs"
"tg-mc/models"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type Auth interface {
IsAuthed(u models.User) bool
RequestAuth(u models.User, req *LoginRequest)
Reject(u models.User)
SetAuth(u models.User)
}
type Authcator struct {
UserMap *sync.Map
}
var authcator *Authcator
func GetAuthcator() Auth {
if authcator == nil {
authcator = &Authcator{
UserMap: &sync.Map{},
}
}
return authcator
}
func (a *Authcator) getUserLoginReq(u models.User) *LoginRequest {
chAny, ok := a.UserMap.Load(u.MCName)
if !ok {
return nil
}
loginReq, ok := chAny.(*LoginRequest)
if !ok {
return nil
}
return loginReq
}
func (a *Authcator) IsAuthed(u models.User) bool {
loginReq := a.getUserLoginReq(u)
if loginReq == nil {
return false
}
return <-loginReq.Resolve
}
func (a *Authcator) RequestAuth(u models.User, req *LoginRequest) {
a.UserMap.Store(u.MCName, req)
m := tgbotapi.NewMessage(u.TGID, fmt.Sprintf("MC用户%v 尝试登录,请选择操作", u.MCName))
m.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("批准", defs.NewApproveCommand(u.MCName).ToJSON()),
tgbotapi.NewInlineKeyboardButtonData("拒绝", defs.NewRejectCommand(u.MCName).ToJSON())),
)
conf.Bot.Send(m)
}
func (a *Authcator) Reject(u models.User) {
loginReq := a.getUserLoginReq(u)
if loginReq == nil {
return
}
loginReq.Resolve <- false
}
func (a *Authcator) SetAuth(u models.User) {
loginReq := a.getUserLoginReq(u)
if loginReq == nil {
return
}
loginReq.Resolve <- true
}

214
services/gateway/gw.go Normal file
View File

@@ -0,0 +1,214 @@
package gateway
import (
"errors"
"fmt"
"strconv"
"tg-mc/conf"
"tg-mc/models"
"time"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/Tnze/go-mc/offline"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
type LoginRequest struct {
ChatID int64
Resolve chan bool
}
func requestVerify(mcid string) (bool, error) {
user, err := models.GetUserByMCName(mcid)
if err != nil {
return false, errors.New("mcid not found in settings")
}
// Send a message to the user to accept or reject using your bot logic
logrus.Infof("Requesting verification for MCID: %s with ChatID: %d\n", mcid, user.TGID)
// The channel to await a response from the verification process
loginRequest := &LoginRequest{
ChatID: user.TGID,
Resolve: make(chan bool),
}
GetAuthcator().RequestAuth(user, loginRequest)
select {
case result := <-loginRequest.Resolve:
return result, nil
case <-time.After(10 * time.Second): // Replace with your actual timeout handling logic
return false, nil
}
}
// This function should be called when a new client connection is accepted
func HandleClientConnection(clientConn net.Conn) {
settings := conf.GetBotSettings().GatewaySettings
targetConns, err := net.DialMC(settings.ServerHost + ":" + strconv.Itoa(settings.ServerPort))
targetConn := *targetConns
// targetConn, err := gonet.Dial("tcp", settings.ServerHost+":"+strconv.Itoa(settings.ServerPort))
if err != nil {
logrus.Errorf("Failed to connect to target: %s", err)
return
}
protocol, intention, err := handshake(clientConn, targetConn)
if err != nil {
logrus.Errorf("Handshake error: %v", err)
return
}
logrus.Infof("Protocol: %v, Intention: %v", protocol, intention)
switch intention {
default: // unknown error
logrus.Errorf("Unknown handshake intention: %v", intention)
case 1: // for status
handleProxyConnection(clientConn, targetConn)
case 2: // for login
handlePlaying(clientConn, targetConn)
}
}
type PlayerInfo struct {
Name string
UUID uuid.UUID
OPLevel int
}
func handlePlaying(conn, target net.Conn) {
// login, get player info
info, err := acceptLogin(conn, target)
if err != nil {
logrus.Errorf("user [%s] Login failed", info.Name)
return
}
logrus.Infof("Login successful: %s", info.Name)
// Write LoginSuccess packet
handleProxyConnection(conn, target)
}
// acceptLogin check player's account
func acceptLogin(conn, target net.Conn) (info PlayerInfo, err error) {
// login start
var p pk.Packet
err = conn.ReadPacket(&p)
if err != nil {
return
}
err = p.Scan((*pk.String)(&info.Name)) // decode username as pk.String
if err != nil {
return
}
if ok, err := requestVerify(info.Name); !ok || err != nil {
err = errors.New("verify failed")
conn.Close()
target.Close()
return info, err
}
if err := target.WritePacket(p); err != nil {
return info, err
}
// auth
const OnlineMode = false
if OnlineMode {
info.UUID = offline.NameToUUID(info.Name)
} else {
// offline-mode UUID
info.UUID = offline.NameToUUID(info.Name)
}
return
}
func handshake(conn, target net.Conn) (protocol, intention int32, err error) {
var (
p pk.Packet
Protocol, Intention pk.VarInt
ServerAddress pk.String // ignored
ServerPort pk.UnsignedShort // ignored
)
// receive handshake packet
if err = conn.ReadPacket(&p); err != nil {
return
}
err = p.Scan(&Protocol, &ServerAddress, &ServerPort, &Intention)
if err != nil {
return
}
err = target.WritePacket(p)
return int32(Protocol), int32(Intention), err
}
func handleProxyConnection(clientConn net.Conn, targetConn net.Conn) {
defer clientConn.Close()
go func() {
for {
var p pk.Packet
if err := clientConn.ReadPacket(&p); err != nil {
logrus.Errorf("ReadPacket error: %v", err)
break
}
if err := targetConn.WritePacket(p); err != nil {
logrus.Errorf("WritePacket error: %v", err)
break
}
}
}()
func() {
for {
var p pk.Packet
if err := targetConn.ReadPacket(&p); err != nil {
logrus.Errorf("ReadPacket error: %v", err)
break
}
if err := clientConn.WritePacket(p); err != nil {
logrus.Errorf("WritePacket error: %v", err)
break
}
}
}()
}
func startProxyServer(proxyHost string, proxyPort int) {
settings := conf.GetBotSettings().GatewaySettings
l, err := net.ListenMC(fmt.Sprintf(":%d", settings.ProxyPort))
if err != nil {
logrus.Fatalf("Error starting proxy server: %s", err)
}
defer l.Close()
logrus.Infof("Proxy server started on %s:%d", proxyHost, proxyPort)
for {
clientConn, err := l.Accept()
if err != nil {
logrus.Errorf("Error accepting connection: %s", err)
continue
}
go HandleClientConnection(clientConn)
}
}
func StartGateway() {
settings := conf.GetBotSettings().GatewaySettings
startProxyServer(settings.ProxyHost, settings.ProxyPort)
}

View File

@@ -1,18 +1,28 @@
package services
import (
"tg-mc/conf"
"tg-mc/services/gateway"
"tg-mc/services/mc"
"tg-mc/services/tgbot"
"tg-mc/services/utils"
)
func Run() {
go func() {
for {
if err := mc.Run(); err != nil {
utils.SendMsg("致命错误:" + err.Error())
}
settings := conf.GetBotSettings()
if settings.EnableGateway {
go gateway.StartGateway()
}
if settings.EnableBridge {
go mc.StartBridgeClient()
}
if settings.EnableBot {
if settings.EnableBridge {
tgbot.Run(mc.SendMsg, mc.SendCommand)
} else {
tgbot.Run(mc.SendMsg, func(s string) error { return nil })
}
}()
tgbot.Run(mc.SendMsg, mc.SendCommand)
}
}

View File

@@ -1,53 +0,0 @@
package mc
// import (
// "sync"
// "tg-mc/models"
// "time"
// )
// type Auth interface {
// IsAuthed(u models.User, expireMode bool) bool
// Auth(u models.User)
// Reject(u models.User)
// }
// type Authcator struct {
// UserMap *sync.Map
// }
// var authcator *Authcator
// func GetAuthcator() Auth {
// if authcator == nil {
// authcator = &Authcator{
// UserMap: &sync.Map{},
// }
// }
// return authcator
// }
// func (a *Authcator) IsAuthed(u models.User, expireMode bool) bool {
// return true
// // if u.MCName != "VaalaCat" {
// // return true
// // }
// if approveTime, ok := a.UserMap.Load(u.MCName); ok {
// if !expireMode {
// return true
// } else if time.Since(approveTime.(time.Time)) < 30*time.Second {
// return true
// } else {
// return false
// }
// }
// return false
// }
// func (a *Authcator) Auth(u models.User) {
// a.UserMap.Store(u.MCName, time.Now())
// }
// func (a *Authcator) Reject(u models.User) {
// a.UserMap.Delete(u.MCName)
// }

View File

@@ -5,6 +5,7 @@ import (
"log"
"strings"
"tg-mc/conf"
"tg-mc/services/utils"
"time"
"github.com/Tnze/go-mc/bot"
@@ -17,6 +18,15 @@ import (
"github.com/sirupsen/logrus"
)
func StartBridgeClient() {
for {
if err := Run(); err != nil {
utils.SendMsg("致命错误:" + err.Error())
}
time.Sleep(time.Second * 5)
}
}
func Run() error {
conf.Client = bot.NewClient()
conf.Client.Auth.Name = conf.GetBotSettings().MCBotName
@@ -44,7 +54,8 @@ func Run() error {
)
if err != nil {
log.Fatal(err)
log.Printf("joinserver error: %v", err)
return err
}
log.Println("Login success")

View File

@@ -1,5 +1,33 @@
package tgbot
import (
"fmt"
"tg-mc/conf"
"tg-mc/defs"
"tg-mc/models"
"tg-mc/services/gateway"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/sirupsen/logrus"
)
func ApproveHandler(update tgbotapi.Update, cmd defs.Command) {
u, err := models.GetUserByTGID(update.CallbackQuery.From.ID)
if err != nil {
return
}
gateway.GetAuthcator().SetAuth(u)
callback := tgbotapi.NewCallback(update.CallbackQuery.ID, "已授权")
if _, err := conf.Bot.Request(callback); err != nil {
logrus.Panic(err)
}
conf.Bot.Send(tgbotapi.NewDeleteMessage(update.CallbackQuery.Message.Chat.ID,
update.CallbackQuery.Message.MessageID))
conf.Bot.Send(tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID,
fmt.Sprintf("已授权☑️: %s 登录MC", u.MCName)))
}
// func ApproveHandler(update tgbotapi.Update, cmd defs.Command) {
// u, err := models.GetUserByTGID(update.CallbackQuery.From.ID)
// if err != nil {

View File

@@ -23,8 +23,8 @@ var funcHandlers = map[string]func(*tgbotapi.Message, interface{}){
}
var callBackHandlers = map[string]func(tgbotapi.Update, defs.Command){
// defs.CMD_APPROVE: ApproveHandler,
// defs.CMD_REJECT: RejectHandler,
defs.CMD_APPROVE: ApproveHandler,
defs.CMD_REJECT: RejectHandler,
}
func init() {

View File

@@ -5,6 +5,7 @@ import (
"tg-mc/conf"
"tg-mc/defs"
"tg-mc/models"
"tg-mc/services/gateway"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/sirupsen/logrus"
@@ -15,6 +16,8 @@ func RejectHandler(update tgbotapi.Update, cmd defs.Command) {
if err != nil {
return
}
gateway.GetAuthcator().Reject(u)
callback := tgbotapi.NewCallback(update.CallbackQuery.ID, "已拒绝")
if _, err := conf.Bot.Request(callback); err != nil {
logrus.Panic(err)