minecraft-server-invites/internal/transport/http.go

470 lines
12 KiB
Go
Raw Normal View History

package transport
import (
"context"
"crypto/rand"
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"whitelistmanager/internal/auth"
"whitelistmanager/internal/invite"
"whitelistmanager/internal/minecraft"
"whitelistmanager/internal/store"
2022-07-16 02:44:32 +01:00
"github.com/alexedwards/flow"
"github.com/google/uuid"
)
2024-06-02 16:02:11 +01:00
type CONTEXT_USER struct {}
type Handler struct {
store store.Storer
manager invite.InviteManager
2022-07-16 02:16:14 +01:00
mc minecraft.Minecraft
}
type Handle interface {
AuthMiddleware(next http.Handler) http.Handler
Cors(next http.Handler) http.Handler
CurrentUser(w http.ResponseWriter, r *http.Request)
CreateInvite(w http.ResponseWriter, r *http.Request)
GetInvite(w http.ResponseWriter, r *http.Request)
DeleteInvite(w http.ResponseWriter, r *http.Request)
AcceptInvite(w http.ResponseWriter, r *http.Request)
AuthRedirect(w http.ResponseWriter, r *http.Request)
AuthCallback(w http.ResponseWriter, r *http.Request)
Server(w http.ResponseWriter, r *http.Request)
Servers(w http.ResponseWriter, r *http.Request)
CreateServer(w http.ResponseWriter, r *http.Request)
DeleteServer(w http.ResponseWriter, r *http.Request)
ServerInvites(w http.ResponseWriter, r *http.Request)
InviteLog(w http.ResponseWriter, r *http.Request)
}
2022-07-16 02:16:14 +01:00
func New(store store.Storer, im invite.InviteManager, mc minecraft.Minecraft) *Handler {
return &Handler{
store: store,
manager: im,
2022-07-16 02:16:14 +01:00
mc: mc,
}
}
func (h *Handler) Cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization")
w.Header().Set("Access-Control-Allow-Methods", "*")
next.ServeHTTP(w, r)
})
}
func (h *Handler) SessionAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := r.Cookie("session")
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
user, err := h.store.SessionUser(session.Value)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
2024-06-02 16:02:11 +01:00
ctx := context.WithValue(r.Context(), CONTEXT_USER{}, user)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
type invitePayload struct {
Server string `json:"server"`
Unlimited bool `json:"unlimited"`
Uses int `json:"uses"`
}
func (h *Handler) CreateInvite(w http.ResponseWriter, r *http.Request) {
var i invitePayload
err := json.NewDecoder(r.Body).Decode(&i)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
in, err := h.manager.Create(store.Invite{
Server: store.Server{Id: i.Server},
Unlimited: i.Unlimited,
Uses: i.Uses,
}, user)
if err != nil {
if err.Error() == invite.NotOwnerofServer {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2024-06-02 16:02:11 +01:00
_, err = w.Write([]byte(in))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h *Handler) GetInvite(w http.ResponseWriter, r *http.Request) {
token := flow.Param(r.Context(), "id")
in, err := h.store.GetInvite(token)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "invalid invite", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = json.NewEncoder(w).Encode(in)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h *Handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
token := flow.Param(r.Context(), "id")
in, err := h.store.GetInvite(token)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "invalid invite", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
remainingUse, err := h.manager.RemainingUses(in)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if remainingUse == 0 {
http.Error(w, "no more uses remaining for invite", http.StatusForbidden)
return
}
server, err := h.store.GetServer(in.Server.Id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
log.Println(user.DisplayName)
2022-07-16 02:16:14 +01:00
resp, err := h.mc.Whitelist(user.DisplayName, server)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if resp == fmt.Sprintf("Added %s to the whitelist", user.DisplayName) {
2022-07-06 17:55:18 +01:00
err = h.store.LogInviteUse(user, in)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
2024-06-02 16:02:11 +01:00
_, err = w.Write([]byte(resp))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h *Handler) AuthRedirect(w http.ResponseWriter, r *http.Request) {
state := store.OauthState{
Id: uuid.New().String(),
Origin: r.Header.Get("Referer"),
State: "started",
}
err := h.store.SaveOauthState(state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, auth.LoginUrl(state.Id), http.StatusTemporaryRedirect)
}
func (h *Handler) AuthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query()["code"][0]
stateId := r.URL.Query()["state"][0]
state, err := h.store.OauthState(stateId)
2024-06-02 16:02:11 +01:00
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
2024-06-02 16:02:11 +01:00
if state.State == "completed" {
return
}
user, err := auth.Authenticate(code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = h.store.SaveUser(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
sessToken, err := generateSessionToken()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sessToken,
HttpOnly: true,
Path: "/",
})
err = h.store.SaveSession(sessToken, user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
state.State = "completed"
err = h.store.SaveOauthState(state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, state.Origin, http.StatusTemporaryRedirect)
}
func generateSessionToken() (string, error) {
b := make([]byte, 64)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), err
}
func (h *Handler) Servers(w http.ResponseWriter, r *http.Request) {
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
if r.Method == http.MethodGet {
servers, err := h.store.GetUserServers(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2022-07-07 18:28:09 +01:00
if len(servers) == 0 {
w.WriteHeader(http.StatusNotFound)
2024-06-02 16:02:11 +01:00
_, err = w.Write([]byte("[]"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
err = json.NewEncoder(w).Encode(servers)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
2022-07-07 18:28:09 +01:00
return
}
return
}
var server store.Server
err := json.NewDecoder(r.Body).Decode(&server)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
server.Id = uuid.New().String()
server.Owner.Id = user.Id
err = h.store.SaveServer(server)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "created server %s", server.Id)
}
func (h *Handler) Server(w http.ResponseWriter, r *http.Request) {
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
serverId := flow.Param(r.Context(), "id")
if r.Method == http.MethodGet {
server, err := h.store.GetServer(serverId)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if server.Owner.Id != user.Id {
http.Error(w, "not owner of server", http.StatusForbidden)
return
}
server.Rcon.Password = ""
2024-06-02 16:02:11 +01:00
err = json.NewEncoder(w).Encode(server)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
return
}
if r.Method == http.MethodDelete {
serverId := flow.Param(r.Context(), "id")
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
server, err := h.store.GetServer(serverId)
if err != nil {
http.Error(w, "no such server", http.StatusNotFound)
return
}
if server.Owner.Id != user.Id {
http.Error(w, "user not server owner", http.StatusForbidden)
return
}
err = h.store.DeleteServer(server, user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2024-06-02 16:02:11 +01:00
_, err = w.Write([]byte("deleted"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
func (h *Handler) CurrentUser(w http.ResponseWriter, r *http.Request) {
2024-06-02 16:02:11 +01:00
value := r.Context().Value(CONTEXT_USER{})
if value == nil {
http.Error(w, "", http.StatusForbidden)
return
}
user := value.(store.User)
2024-06-02 16:02:11 +01:00
_, err := w.Write([]byte(user.DisplayName))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h *Handler) ServerInvites(w http.ResponseWriter, r *http.Request) {
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
serverId := flow.Param(r.Context(), "id")
server, err := h.store.GetServer(serverId)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if server.Owner.Id != user.Id {
http.Error(w, "not owner of server", http.StatusForbidden)
return
}
serverInvites, err := h.store.ServerInvites(server)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if server.Owner.Id != user.Id {
http.Error(w, "not owner of server", http.StatusForbidden)
return
}
2022-07-07 18:28:09 +01:00
if len(serverInvites) == 0 {
w.WriteHeader(http.StatusNotFound)
2024-06-02 16:02:11 +01:00
_, err := w.Write([]byte("[]"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2022-07-07 18:28:09 +01:00
return
}
2024-06-02 16:02:11 +01:00
err = json.NewEncoder(w).Encode(serverInvites)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h *Handler) InviteLog(w http.ResponseWriter, r *http.Request) {
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
inviteToken := flow.Param(r.Context(), "id")
invite, err := h.store.GetInvite(inviteToken)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if invite.Creator.Id != user.Id {
http.Error(w, "not owner of invite", http.StatusForbidden)
return
}
logs, err := h.store.InviteLog(invite)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2022-07-07 18:28:09 +01:00
if len(logs) == 0 {
w.WriteHeader(http.StatusNotFound)
2024-06-02 16:02:11 +01:00
_, err = w.Write([]byte("[]"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2022-07-07 18:28:09 +01:00
return
}
2024-06-02 16:02:11 +01:00
err = json.NewEncoder(w).Encode(logs)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (h *Handler) DeleteInvite(w http.ResponseWriter, r *http.Request) {
2024-06-02 16:02:11 +01:00
user := r.Context().Value(CONTEXT_USER{}).(store.User)
inviteToken := flow.Param(r.Context(), "id")
invite, err := h.store.GetInvite(inviteToken)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if invite.Creator.Id != user.Id {
http.Error(w, "not owner of invite", http.StatusForbidden)
return
}
err = h.store.DeleteInvite(invite)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2024-06-02 16:02:11 +01:00
_, err = w.Write([]byte("deleted"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}