minecraft-server-invites/transport/http.go
Gabriel Simmer 709353b46e Store oauth session for redirect, invite un-authed
Also added Dockerfile :)
2022-07-06 00:17:41 +01:00

280 lines
6.9 KiB
Go

package transport
import (
"context"
"crypto/rand"
"database/sql"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/alexedwards/flow"
"github.com/google/uuid"
"log"
"net/http"
"whitelistmanager/auth"
"whitelistmanager/invite"
"whitelistmanager/minecraft"
"whitelistmanager/store"
)
type Handler struct {
store store.Storer
manager invite.InviteManager
}
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)
AcceptInvite(w http.ResponseWriter, r *http.Request)
AuthRedirect(w http.ResponseWriter, r *http.Request)
AuthCallback(w http.ResponseWriter, r *http.Request)
CreateServer(w http.ResponseWriter, r *http.Request)
}
func New(store store.Storer) *Handler {
im := invite.NewManager(store)
return &Handler{
store: store,
manager: im,
}
}
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
}
ctx := context.WithValue(r.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
}
user := r.Context().Value("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
}
w.Write([]byte(in))
}
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
}
user := r.Context().Value("user").(store.User)
log.Println(user.DisplayName)
resp, err := minecraft.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) {
err = h.store.LogInviteUse(user.DisplayName, in)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
w.Write([]byte(resp))
}
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)
if err != nil || state.State == "completed" {
http.Error(w, err.Error(), http.StatusForbidden)
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) Server(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("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
}
json.NewEncoder(w).Encode(servers)
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) CurrentUser(w http.ResponseWriter, r *http.Request) {
value := r.Context().Value("user")
if value == nil {
http.Error(w, "", http.StatusForbidden)
return
}
user := value.(store.User)
w.Write([]byte(user.DisplayName))
}