Add hooks

Signed-off-by: Etzelia <etzelia@hotmail.com>
main
Etzelia 2021-08-10 20:16:19 -05:00
parent dd7953b452
commit 6155f28671
No known key found for this signature in database
GPG Key ID: 708511AE7ABC5314
12 changed files with 185 additions and 31 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
.idea/ .idea/
/mcm-register* /mineauth*

24
README.md 100644
View File

@ -0,0 +1,24 @@
# mineauth
Users can log into a server (which validates their account) and you can run hooks after.
```text
Usage of mineauth:
-community string
URL to community
-debug
Debug Logging
-hook value
Hook to run
-ping string
Message for the server list (default "Login to authenticate!")
-port int
Port to listen on (default 25565)
-timeout int
HTTP timeout (default 15)
```
## License
[MIT](LICENSE)

View File

@ -0,0 +1,76 @@
package main
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"net/http"
"os"
)
//go:embed webhook.txt
var webhookURL string
type profile struct {
ID string `json:"id"`
Name string `json:"name"`
IP string `json:"ip"`
}
func (p *profile) UUID() string {
return fmt.Sprintf("%s-%s-%s-%s-%s", p.ID[:8], p.ID[8:12], p.ID[12:16], p.ID[16:20], p.ID[20:32])
}
func main() {
var profile profile
if err := json.NewDecoder(os.Stdin).Decode(&profile); err != nil {
panic(err)
}
payloadHook := webhook{
Username: profile.Name,
Embeds: []embed{
{
Fields: []field{
{
Name: "UUID",
Value: profile.UUID(),
},
{
Name: "IP",
Value: profile.IP,
},
},
},
},
}
payload, err := json.Marshal(payloadHook)
if err != nil {
panic(err)
}
res, err := http.Post(webhookURL, "application/json", bytes.NewReader(payload))
if err != nil {
panic(err)
}
if res.StatusCode != http.StatusNoContent {
panic(fmt.Errorf("received non-200 status: %s", res.Status))
}
}
type webhook struct {
Username string `json:"username"`
Embeds []embed `json:"embeds"`
}
type embed struct {
Fields []field `json:"fields"`
}
type field struct {
Name string `json:"name"`
Value string `json:"value"`
}

View File

@ -0,0 +1 @@
https://discord.com/api/webhooks/<id>/<token>

2
go.mod
View File

@ -1,4 +1,4 @@
module git.canopymc.net/Etzelia/mcm-register module git.canopymc.net/Etzelia/mineauth
go 1.16 go 1.16

27
main.go
View File

@ -2,19 +2,27 @@ package main
import ( import (
"flag" "flag"
"github.com/peterbourgon/ff/v3" "git.canopymc.net/Etzelia/mineauth/server"
"github.com/peterbourgon/ff/v3/fftoml"
"go.jolheiser.com/beaver"
"net/http" "net/http"
"os" "os"
"time" "time"
"github.com/peterbourgon/ff/v3"
"github.com/peterbourgon/ff/v3/fftoml"
"go.jolheiser.com/beaver"
) )
func main() { func main() {
fs := flag.NewFlagSet("afk", flag.ExitOnError) fs := flag.NewFlagSet("mineauth", flag.ExitOnError)
portFlag := fs.Int("port", 25565, "Port to listen on") portFlag := fs.Int("port", 25565, "Port to listen on")
timeoutFlag := fs.Int("timeout", 15, "HTTP timeout") timeoutFlag := fs.Int("timeout", 15, "HTTP timeout")
discordFlag := fs.String("discord", "", "Discord invite link") pingMessageFlag := fs.String("ping", "Login to authenticate!", "Message for the server list")
communityFlag := fs.String("community", "", "URL to community")
hooksFlag := make([]string, 0)
fs.Func("hook", "Hook to run", func(hook string) error {
hooksFlag = append(hooksFlag, hook)
return nil
})
debugFlag := fs.Bool("debug", false, "Debug Logging") debugFlag := fs.Bool("debug", false, "Debug Logging")
if err := ff.Parse(fs, os.Args[1:], if err := ff.Parse(fs, os.Args[1:],
ff.WithEnvVarPrefix("MCM_REGISTER"), ff.WithEnvVarPrefix("MCM_REGISTER"),
@ -31,13 +39,18 @@ func main() {
http.DefaultClient.Timeout = time.Second * time.Duration(*timeoutFlag) http.DefaultClient.Timeout = time.Second * time.Duration(*timeoutFlag)
server, err := NewServer(*discordFlag) serv, err := server.New(server.Options{
PingMessage: *pingMessageFlag,
CommunityURL: *communityFlag,
Hooks: hooksFlag,
})
if err != nil { if err != nil {
beaver.Error(err) beaver.Error(err)
return return
} }
if err := server.Start(*portFlag); err != nil { beaver.Infof("Listening on http://localhost:%d", *portFlag)
if err := serv.Start(*portFlag); err != nil {
beaver.Error(err) beaver.Error(err)
} }
} }

View File

@ -1,4 +1,4 @@
package main package server
import "crypto/cipher" import "crypto/cipher"

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"crypto/sha1" "crypto/sha1"

View File

@ -1,4 +1,4 @@
package main package server
import ( import (
"bytes" "bytes"
@ -9,11 +9,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
) )
var hasJoinedURL = func() *url.URL { var hasJoinedURL = func() *url.URL {
@ -37,6 +38,7 @@ func (s *Server) encryptionRequest(conn net.Conn) ([]byte, error) {
type profile struct { type profile struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
IP string `json:"ip"`
} }
func (p *profile) UUID() string { func (p *profile) UUID() string {

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,9 +1,10 @@
package main package server
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Tnze/go-mc/bot" "github.com/Tnze/go-mc/bot"
"github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/net" "github.com/Tnze/go-mc/net"
@ -22,7 +23,7 @@ func (s *Server) acceptListPing(conn net.Conn) {
switch p.ID { switch p.ID {
case 0x00: case 0x00:
err = conn.WritePacket(pk.Marshal(0x00, pk.String(listResp()))) err = conn.WritePacket(pk.Marshal(0x00, pk.String(s.listResp())))
case 0x01: case 0x01:
err = conn.WritePacket(p) err = conn.WritePacket(p)
} }
@ -38,7 +39,7 @@ type player struct {
} }
// listResp return server status as JSON string // listResp return server status as JSON string
func listResp() string { func (s *Server) listResp() string {
var list struct { var list struct {
Version struct { Version struct {
Name string `json:"name"` Name string `json:"name"`
@ -58,7 +59,7 @@ func listResp() string {
list.Players.Max = 0 list.Players.Max = 0
list.Players.Online = 0 list.Players.Online = 0
list.Players.Sample = []player{} list.Players.Sample = []player{}
list.Description = chat.Message{Text: "Login to register!"} list.Description = chat.Message{Text: s.opts.PingMessage}
list.FavIcon = fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(favicon)) list.FavIcon = fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(favicon))
data, err := json.Marshal(list) data, err := json.Marshal(list)

View File

@ -1,28 +1,39 @@
package main package server
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
_ "embed" _ "embed"
"encoding/json"
"fmt" "fmt"
"github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/net" "github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet" pk "github.com/Tnze/go-mc/net/packet"
"github.com/google/uuid" "github.com/google/uuid"
"go.jolheiser.com/beaver" "go.jolheiser.com/beaver"
gonet "net"
"os/exec"
"strings"
) )
//go:embed favicon.png //go:embed favicon.png
var favicon []byte var favicon []byte
type Server struct { type Server struct {
privateKey *rsa.PrivateKey privateKey *rsa.PrivateKey
publicKey []byte publicKey []byte
discordInvite string opts Options
} }
func NewServer(discordInvite string) (*Server, error) { type Options struct {
CommunityURL string
PingMessage string
Hooks []string
}
func New(opts Options) (*Server, error) {
private, err := rsa.GenerateKey(rand.Reader, 1024) private, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil { if err != nil {
return nil, fmt.Errorf("error generate private key: %v", err) return nil, fmt.Errorf("error generate private key: %v", err)
@ -36,14 +47,13 @@ func NewServer(discordInvite string) (*Server, error) {
private.Precompute() private.Precompute()
return &Server{ return &Server{
privateKey: private, privateKey: private,
publicKey: public, publicKey: public,
discordInvite: discordInvite, opts: opts,
}, nil }, nil
} }
func (s *Server) Start(port int) error { func (s *Server) Start(port int) error {
beaver.Infof("Listening on http://localhost:%d", port)
l, err := net.ListenMC(fmt.Sprintf(":%d", port)) l, err := net.ListenMC(fmt.Sprintf(":%d", port))
if err != nil { if err != nil {
return fmt.Errorf("listen error: %v", err) return fmt.Errorf("listen error: %v", err)
@ -96,7 +106,34 @@ func (s *Server) handlePlaying(conn net.Conn) {
return return
} }
// TODO Register profile.IP, _, err = gonet.SplitHostPort(conn.Socket.RemoteAddr().String())
if err != nil {
beaver.Errorf("could not get user IP: %v", err)
return
}
payload, err := json.Marshal(profile)
if err != nil {
beaver.Errorf("could not marshal payload: %v", err)
return
}
for _, hook := range s.opts.Hooks {
go func(h string) {
s := strings.Split(h, " ")
var args []string
if len(s) > 1 {
args = s[1:]
}
cmd := exec.Command(s[0], args...)
cmd.Stdin = bytes.NewReader(payload)
out, err := cmd.CombinedOutput()
if err != nil {
beaver.Errorf("could not run hook `%s`: %v", h, err)
beaver.Warn(string(out))
}
}(hook)
}
econn, err := encryptedConn(conn, secret) econn, err := encryptedConn(conn, secret)
if err != nil { if err != nil {
@ -104,9 +141,9 @@ func (s *Server) handlePlaying(conn net.Conn) {
return return
} }
msg := fmt.Sprintf("Thanks for registering, %s!", profile.Name) msg := fmt.Sprintf("Thanks for authenticating, %s!", profile.Name)
if s.discordInvite != "" { if s.opts.CommunityURL != "" {
msg += fmt.Sprintf("\n\nJoin the Discord\n%s", s.discordInvite) msg += fmt.Sprintf("\n\nJoin the community\n%s", s.opts.CommunityURL)
} }
packet := pk.Marshal(0x00, packet := pk.Marshal(0x00,
chat.Message{Text: msg}, chat.Message{Text: msg},