mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-04-24 07:27:31 -05:00
Rework ban, kick, unban APIs. Add motd API.
This commit is contained in:
parent
cfec099bdd
commit
d1dd5816c2
142
api/ban.go
142
api/ban.go
|
|
@ -2,8 +2,8 @@ package api
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/database"
|
||||
|
|
@ -11,118 +11,92 @@ import (
|
|||
)
|
||||
|
||||
func HandleBan(w http.ResponseWriter, r *http.Request) {
|
||||
errorString, ip := handleBanImpl(w, r)
|
||||
if errorString != "" {
|
||||
jsonData, _ := json.Marshal(map[string]string{"error": errorString})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
var jsonData map[string]string
|
||||
var statusCode int
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodHead:
|
||||
statusCode = http.StatusOK
|
||||
case http.MethodPost:
|
||||
jsonData, statusCode = handleBanImpl(w, r)
|
||||
default:
|
||||
jsonData = mmss("error", "Incorrect request. POST or HEAD only.")
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
if len(jsonData) == 0 {
|
||||
w.WriteHeader(statusCode)
|
||||
} else {
|
||||
jsonData, _ := json.Marshal(map[string]string{"success": "true", "ip": ip})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
json, _ := json.Marshal(jsonData)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(json)))
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(json)
|
||||
}
|
||||
}
|
||||
|
||||
func handleBanImpl(w http.ResponseWriter, r *http.Request) (string, string) {
|
||||
type BanRequestSpec struct {
|
||||
Secret string
|
||||
Pid uint32
|
||||
Days uint64
|
||||
Hours uint64
|
||||
Minutes uint64
|
||||
Tos bool
|
||||
Reason string
|
||||
ReasonHidden string
|
||||
Moderator string
|
||||
}
|
||||
|
||||
func handleBanImpl(w http.ResponseWriter, r *http.Request) (map[string]string, int) {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
// TODO: Use POST instead of GET
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return "Bad request", ""
|
||||
return mmss("error", "Unable to read request body"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
var req BanRequestSpec
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return "Bad request", ""
|
||||
return mmss("error", err.Error()), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if apiSecret == "" || query.Get("secret") != apiSecret {
|
||||
return "Invalid API secret", ""
|
||||
if apiSecret == "" || req.Secret != apiSecret {
|
||||
return mmss("error", "Invalid API secret in request"), http.StatusUnauthorized
|
||||
}
|
||||
|
||||
pidStr := query.Get("pid")
|
||||
if pidStr == "" {
|
||||
return "Missing pid in request", ""
|
||||
if req.Pid == 0 {
|
||||
return mmss("error", "pid missing or 0 in request"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseUint(pidStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid pid", ""
|
||||
if req.Reason == "" {
|
||||
return mmss("error", "Missing ban reason in request"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
tosStr := query.Get("tos")
|
||||
if tosStr == "" {
|
||||
return "Missing tos in request", ""
|
||||
}
|
||||
|
||||
tos, err := strconv.ParseBool(tosStr)
|
||||
if err != nil {
|
||||
return "Invalid tos", ""
|
||||
}
|
||||
|
||||
minutes := uint64(0)
|
||||
if query.Get("minutes") != "" {
|
||||
minutesStr := query.Get("minutes")
|
||||
minutes, err = strconv.ParseUint(minutesStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid minutes", ""
|
||||
}
|
||||
}
|
||||
|
||||
hours := uint64(0)
|
||||
if query.Get("hours") != "" {
|
||||
hoursStr := query.Get("hours")
|
||||
hours, err = strconv.ParseUint(hoursStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid hours", ""
|
||||
}
|
||||
}
|
||||
|
||||
days := uint64(0)
|
||||
if query.Get("days") != "" {
|
||||
daysStr := query.Get("days")
|
||||
days, err = strconv.ParseUint(daysStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid days", ""
|
||||
}
|
||||
}
|
||||
|
||||
reason := query.Get("reason")
|
||||
if "reason" == "" {
|
||||
return "Missing ban reason", ""
|
||||
}
|
||||
|
||||
// reason_hidden is optional
|
||||
reasonHidden := query.Get("reason_hidden")
|
||||
|
||||
moderator := query.Get("moderator")
|
||||
if "moderator" == "" {
|
||||
moderator := req.Moderator
|
||||
if moderator == "" {
|
||||
moderator = "admin"
|
||||
}
|
||||
|
||||
minutes = days*24*60 + hours*60 + minutes
|
||||
minutes := req.Days*24*60 + req.Hours*60 + req.Minutes
|
||||
if minutes == 0 {
|
||||
return "Missing ban length", ""
|
||||
return mmss("error", "Ban length missing or 0"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
length := time.Duration(minutes) * time.Minute
|
||||
|
||||
if !database.BanUser(pool, ctx, uint32(pid), tos, length, reason, reasonHidden, moderator) {
|
||||
return "Failed to ban user", ""
|
||||
if !database.BanUser(pool, ctx, req.Pid, req.Tos, length, req.Reason, req.ReasonHidden, moderator) {
|
||||
return mmss("error", "Failed to ban user"), http.StatusInternalServerError
|
||||
}
|
||||
|
||||
if tos {
|
||||
gpcm.KickPlayer(uint32(pid), "banned")
|
||||
if req.Tos {
|
||||
gpcm.KickPlayer(req.Pid, "banned")
|
||||
} else {
|
||||
gpcm.KickPlayer(uint32(pid), "restricted")
|
||||
gpcm.KickPlayer(req.Pid, "restricted")
|
||||
}
|
||||
|
||||
ip := database.GetUserIP(pool, ctx, uint32(pid))
|
||||
|
||||
return "", ip
|
||||
ip := database.GetUserIP(pool, ctx, req.Pid)
|
||||
return mmss("result", "success", "ip", ip), http.StatusOK
|
||||
}
|
||||
|
|
|
|||
74
api/kick.go
74
api/kick.go
|
|
@ -2,61 +2,69 @@ package api
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
"wwfc/gpcm"
|
||||
)
|
||||
|
||||
func HandleKick(w http.ResponseWriter, r *http.Request) {
|
||||
errorString, ip := handleKickImpl(w, r)
|
||||
if errorString != "" {
|
||||
jsonData, _ := json.Marshal(map[string]string{"error": errorString})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
var jsonData map[string]string
|
||||
var statusCode int
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodHead:
|
||||
statusCode = http.StatusOK
|
||||
case http.MethodPost:
|
||||
jsonData, statusCode = handleKickImpl(w, r)
|
||||
default:
|
||||
jsonData = mmss("error", "Incorrect request. POST or HEAD only.")
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
if len(jsonData) == 0 {
|
||||
w.WriteHeader(statusCode)
|
||||
} else {
|
||||
jsonData, _ := json.Marshal(map[string]string{"success": "true", "ip": ip})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
json, _ := json.Marshal(jsonData)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(json)))
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(json)
|
||||
}
|
||||
}
|
||||
|
||||
func handleKickImpl(w http.ResponseWriter, r *http.Request) (string, string) {
|
||||
type KickRequestSpec struct {
|
||||
Secret string
|
||||
Pid uint32
|
||||
}
|
||||
|
||||
func handleKickImpl(w http.ResponseWriter, r *http.Request) (map[string]string, int) {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
// TODO: Use POST instead of GET
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return "Bad request", ""
|
||||
return mmss("error", "Unable to read request body"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
var req KickRequestSpec
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return "Bad request", ""
|
||||
return mmss("error", err.Error()), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if apiSecret == "" || query.Get("secret") != apiSecret {
|
||||
return "Invalid API secret", ""
|
||||
if apiSecret == "" || req.Secret != apiSecret {
|
||||
return mmss("error", "Invalid API secret in request"), http.StatusUnauthorized
|
||||
}
|
||||
|
||||
pidStr := query.Get("pid")
|
||||
if pidStr == "" {
|
||||
return "Missing pid in request", ""
|
||||
if req.Pid == 0 {
|
||||
return mmss("error", "pid missing or 0 in request"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseUint(pidStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid pid", ""
|
||||
}
|
||||
gpcm.KickPlayer(req.Pid, "moderator_kick")
|
||||
|
||||
gpcm.KickPlayer(uint32(pid), "moderator_kick")
|
||||
|
||||
ip := database.GetUserIP(pool, ctx, uint32(pid))
|
||||
|
||||
return "", ip
|
||||
ip := database.GetUserIP(pool, ctx, req.Pid)
|
||||
return mmss("status", "success", "ip", ip), http.StatusOK
|
||||
}
|
||||
|
|
|
|||
17
api/main.go
17
api/main.go
|
|
@ -35,3 +35,20 @@ func StartServer(reload bool) {
|
|||
|
||||
func Shutdown() {
|
||||
}
|
||||
|
||||
// make map string string
|
||||
func mmss(data ...string) map[string]string {
|
||||
ret := make(map[string]string)
|
||||
|
||||
l := len(data)
|
||||
|
||||
if l%2 != 0 || l == 0 {
|
||||
panic("Length of data must be divisible by two")
|
||||
}
|
||||
|
||||
for i := 0; i < l; i += 2 {
|
||||
ret[data[i]] = data[i+1]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
|||
78
api/motd.go
Normal file
78
api/motd.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"wwfc/gpcm"
|
||||
)
|
||||
|
||||
func HandleMotd(w http.ResponseWriter, r *http.Request) {
|
||||
var jsonData map[string]string
|
||||
var statusCode int
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodHead:
|
||||
statusCode = http.StatusOK
|
||||
case http.MethodGet:
|
||||
motd, err := gpcm.GetMessageOfTheDay()
|
||||
if err != nil {
|
||||
jsonData = mmss("error", err.Error())
|
||||
statusCode = http.StatusInternalServerError
|
||||
break
|
||||
}
|
||||
|
||||
jsonData = mmss("motd", motd)
|
||||
statusCode = http.StatusOK
|
||||
case http.MethodPost:
|
||||
jsonData, statusCode = handleMotdImpl(w, r)
|
||||
default:
|
||||
jsonData = mmss("error", "Incorrect request. POST, GET, or HEAD only.")
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
if len(jsonData) == 0 {
|
||||
w.WriteHeader(statusCode)
|
||||
} else {
|
||||
json, _ := json.Marshal(jsonData)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(json)))
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(json)
|
||||
}
|
||||
}
|
||||
|
||||
type MotdRequestSpec struct {
|
||||
Secret string
|
||||
Motd string
|
||||
}
|
||||
|
||||
func handleMotdImpl(w http.ResponseWriter, r *http.Request) (map[string]string, int) {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return mmss("error", "Unable to read request body"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
var req MotdRequestSpec
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return mmss("error", err.Error()), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if apiSecret == "" || req.Secret != apiSecret {
|
||||
return mmss("error", "Invalid API secret in request"), http.StatusUnauthorized
|
||||
}
|
||||
|
||||
err = gpcm.SetMessageOfTheDay(req.Motd)
|
||||
if err != nil {
|
||||
return mmss("error", err.Error()), http.StatusInternalServerError
|
||||
}
|
||||
|
||||
// Don't return empty JSON, this is placeholder for now.
|
||||
return mmss("result", "success"), http.StatusOK
|
||||
}
|
||||
74
api/unban.go
74
api/unban.go
|
|
@ -2,60 +2,70 @@ package api
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
)
|
||||
|
||||
func HandleUnban(w http.ResponseWriter, r *http.Request) {
|
||||
errorString, ip := handleUnbanImpl(w, r)
|
||||
if errorString != "" {
|
||||
jsonData, _ := json.Marshal(map[string]string{"error": errorString})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
var jsonData map[string]string
|
||||
var statusCode int
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodHead:
|
||||
statusCode = http.StatusOK
|
||||
case http.MethodPost:
|
||||
jsonData, statusCode = handleUnbanImpl(w, r)
|
||||
default:
|
||||
jsonData = mmss("error", "Incorrect request. POST or HEAD only.")
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
if len(jsonData) == 0 {
|
||||
w.WriteHeader(statusCode)
|
||||
} else {
|
||||
jsonData, _ := json.Marshal(map[string]string{"success": "true", "ip": ip})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
w.Write(jsonData)
|
||||
json, _ := json.Marshal(jsonData)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(json)))
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write(json)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUnbanImpl(w http.ResponseWriter, r *http.Request) (string, string) {
|
||||
type UnbanRequestSpec struct {
|
||||
Secret string
|
||||
Pid uint32
|
||||
}
|
||||
|
||||
func handleUnbanImpl(w http.ResponseWriter, r *http.Request) (map[string]string, int) {
|
||||
// TODO: Actual authentication rather than a fixed secret
|
||||
// TODO: Use POST instead of GET
|
||||
|
||||
u, err := url.Parse(r.URL.String())
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return "Bad request", ""
|
||||
return mmss("error", "Unable to read request body"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
query, err := url.ParseQuery(u.RawQuery)
|
||||
var req UnbanRequestSpec
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return "Bad request", ""
|
||||
return mmss("error", err.Error()), http.StatusBadRequest
|
||||
}
|
||||
|
||||
if apiSecret == "" || query.Get("secret") != apiSecret {
|
||||
return "Invalid API secret", ""
|
||||
if apiSecret == "" || req.Secret != apiSecret {
|
||||
return mmss("error", "Invalid API secret in request"), http.StatusUnauthorized
|
||||
}
|
||||
|
||||
pidStr := query.Get("pid")
|
||||
if pidStr == "" {
|
||||
return "Missing pid in request", ""
|
||||
if req.Pid == 0 {
|
||||
return mmss("error", "pid missing or 0 in request"), http.StatusBadRequest
|
||||
}
|
||||
|
||||
pid, err := strconv.ParseUint(pidStr, 10, 32)
|
||||
if err != nil {
|
||||
return "Invalid pid", ""
|
||||
if !database.UnbanUser(pool, ctx, req.Pid) {
|
||||
return mmss("error", "Failed to unban user"), http.StatusInternalServerError
|
||||
}
|
||||
|
||||
database.UnbanUser(pool, ctx, uint32(pid))
|
||||
|
||||
ip := database.GetUserIP(pool, ctx, uint32(pid))
|
||||
|
||||
return "", ip
|
||||
ip := database.GetUserIP(pool, ctx, req.Pid)
|
||||
return mmss("result", "success", "ip", ip), http.StatusOK
|
||||
}
|
||||
|
|
|
|||
25
gpcm/motd.go
25
gpcm/motd.go
|
|
@ -1,16 +1,33 @@
|
|||
package gpcm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
var motdFilepath = "./motd.txt"
|
||||
var motd string = ""
|
||||
|
||||
func GetMessageOfTheDay() (string, error) {
|
||||
contents, err := os.ReadFile(motdFilepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if motd == "" {
|
||||
contents, err := os.ReadFile(motdFilepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
motd = string(contents)
|
||||
}
|
||||
|
||||
return string(contents), nil
|
||||
return motd, nil
|
||||
}
|
||||
|
||||
func SetMessageOfTheDay(nmotd string) error {
|
||||
if nmotd == "" {
|
||||
return errors.New("Motd cannot be empty")
|
||||
}
|
||||
|
||||
err := os.WriteFile(motdFilepath, []byte(nmotd), 0644)
|
||||
motd = nmotd
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,6 +168,11 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/api/motd" {
|
||||
api.HandleMotd(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
replyHTTPError(w, 404, "404 Not Found")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user