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/
/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

27
main.go
View File

@ -2,19 +2,27 @@ package main
import (
"flag"
"github.com/peterbourgon/ff/v3"
"github.com/peterbourgon/ff/v3/fftoml"
"go.jolheiser.com/beaver"
"git.canopymc.net/Etzelia/mineauth/server"
"net/http"
"os"
"time"
"github.com/peterbourgon/ff/v3"
"github.com/peterbourgon/ff/v3/fftoml"
"go.jolheiser.com/beaver"
)
func main() {
fs := flag.NewFlagSet("afk", flag.ExitOnError)
fs := flag.NewFlagSet("mineauth", flag.ExitOnError)
portFlag := fs.Int("port", 25565, "Port to listen on")
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")
if err := ff.Parse(fs, os.Args[1:],
ff.WithEnvVarPrefix("MCM_REGISTER"),
@ -31,13 +39,18 @@ func main() {
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 {
beaver.Error(err)
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)
}
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package main
package server
import (
"bytes"
@ -9,11 +9,12 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
"io"
"net/http"
"net/url"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
)
var hasJoinedURL = func() *url.URL {
@ -37,6 +38,7 @@ func (s *Server) encryptionRequest(conn net.Conn) ([]byte, error) {
type profile struct {
ID string `json:"id"`
Name string `json:"name"`
IP string `json:"ip"`
}
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 (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/Tnze/go-mc/bot"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/net"
@ -22,7 +23,7 @@ func (s *Server) acceptListPing(conn net.Conn) {
switch p.ID {
case 0x00:
err = conn.WritePacket(pk.Marshal(0x00, pk.String(listResp())))
err = conn.WritePacket(pk.Marshal(0x00, pk.String(s.listResp())))
case 0x01:
err = conn.WritePacket(p)
}
@ -38,7 +39,7 @@ type player struct {
}
// listResp return server status as JSON string
func listResp() string {
func (s *Server) listResp() string {
var list struct {
Version struct {
Name string `json:"name"`
@ -58,7 +59,7 @@ func listResp() string {
list.Players.Max = 0
list.Players.Online = 0
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))
data, err := json.Marshal(list)

View File

@ -1,28 +1,39 @@
package main
package server
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
_ "embed"
"encoding/json"
"fmt"
"github.com/Tnze/go-mc/chat"
"github.com/Tnze/go-mc/net"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/google/uuid"
"go.jolheiser.com/beaver"
gonet "net"
"os/exec"
"strings"
)
//go:embed favicon.png
var favicon []byte
type Server struct {
privateKey *rsa.PrivateKey
publicKey []byte
discordInvite string
privateKey *rsa.PrivateKey
publicKey []byte
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)
if err != nil {
return nil, fmt.Errorf("error generate private key: %v", err)
@ -36,14 +47,13 @@ func NewServer(discordInvite string) (*Server, error) {
private.Precompute()
return &Server{
privateKey: private,
publicKey: public,
discordInvite: discordInvite,
privateKey: private,
publicKey: public,
opts: opts,
}, nil
}
func (s *Server) Start(port int) error {
beaver.Infof("Listening on http://localhost:%d", port)
l, err := net.ListenMC(fmt.Sprintf(":%d", port))
if err != nil {
return fmt.Errorf("listen error: %v", err)
@ -96,7 +106,34 @@ func (s *Server) handlePlaying(conn net.Conn) {
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)
if err != nil {
@ -104,9 +141,9 @@ func (s *Server) handlePlaying(conn net.Conn) {
return
}
msg := fmt.Sprintf("Thanks for registering, %s!", profile.Name)
if s.discordInvite != "" {
msg += fmt.Sprintf("\n\nJoin the Discord\n%s", s.discordInvite)
msg := fmt.Sprintf("Thanks for authenticating, %s!", profile.Name)
if s.opts.CommunityURL != "" {
msg += fmt.Sprintf("\n\nJoin the community\n%s", s.opts.CommunityURL)
}
packet := pk.Marshal(0x00,
chat.Message{Text: msg},