mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-05-06 22:43:27 -05:00
169 lines
4.2 KiB
Go
169 lines
4.2 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"strconv"
|
|
)
|
|
|
|
type APIErrorString string
|
|
|
|
const (
|
|
APIErrorAuthenticationFailed APIErrorString = "AuthenticationFailed"
|
|
APIErrorInvalidQuery APIErrorString = "InvalidQuery"
|
|
APIErrorInvalidProfileID APIErrorString = "InvalidProfileID"
|
|
APIErrorInvalidBanReason APIErrorString = "InvalidBanReason"
|
|
APIErrorInvalidBanLength APIErrorString = "InvalidBanLength"
|
|
APIErrorBanFailed APIErrorString = "BanFailed"
|
|
APIErrorInvalidBanQuery APIErrorString = "InvalidBanQuery"
|
|
APIErrorBanNotFound APIErrorString = "BanNotFound"
|
|
)
|
|
|
|
type APIError struct {
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
type Role string
|
|
|
|
// Values currently just made up
|
|
const (
|
|
RoleNone Role = "none" // Not signed in
|
|
RoleUser Role = "user"
|
|
RoleAdmin Role = "admin"
|
|
RoleModerator Role = "moderator"
|
|
)
|
|
|
|
type AuthInfo struct {
|
|
Secret string `json:"secret"`
|
|
}
|
|
|
|
var (
|
|
errOptionsRequest = errors.New("OPTIONS request")
|
|
errIncorrectMethod = errors.New("incorrect HTTP method")
|
|
errNoAuthInfo = errors.New("request struct does not contain AuthInfo fields")
|
|
errAuthFailed = errors.New("authentication failed")
|
|
)
|
|
|
|
func parseGet(r *http.Request, w http.ResponseWriter, requiredRole Role) (query url.Values, err error) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
switch {
|
|
case r.Method == http.MethodGet:
|
|
break
|
|
|
|
case r.Method == http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return nil, errOptionsRequest
|
|
|
|
default:
|
|
w.Header().Set("Allow", "GET, OPTIONS")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return nil, errIncorrectMethod
|
|
}
|
|
|
|
query, err = url.ParseQuery(r.URL.RawQuery)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return nil, err
|
|
}
|
|
|
|
if requiredRole == RoleNone {
|
|
return query, nil
|
|
}
|
|
|
|
authInfo := makeAuthInfo(query)
|
|
if !authenticate(authInfo, requiredRole) {
|
|
replyError(w, http.StatusUnauthorized, APIErrorAuthenticationFailed)
|
|
return nil, errAuthFailed
|
|
}
|
|
return query, nil
|
|
}
|
|
|
|
func parsePost(r *http.Request, w http.ResponseWriter, parsed any, requiredRole Role) error {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
switch {
|
|
case r.Method == http.MethodPost:
|
|
break
|
|
|
|
case r.Method == http.MethodOptions:
|
|
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return errOptionsRequest
|
|
|
|
default:
|
|
w.Header().Set("Allow", "POST, OPTIONS")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return errIncorrectMethod
|
|
}
|
|
|
|
jsonData, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return err
|
|
}
|
|
|
|
err = json.Unmarshal(jsonData, parsed)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return err
|
|
}
|
|
|
|
if requiredRole == RoleNone {
|
|
return nil
|
|
}
|
|
|
|
authInfo, ok := reflect.ValueOf(parsed).Elem().FieldByName("AuthInfo").Interface().(AuthInfo)
|
|
if !ok {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return errNoAuthInfo
|
|
}
|
|
if !authenticate(authInfo, requiredRole) {
|
|
replyError(w, http.StatusUnauthorized, APIErrorAuthenticationFailed)
|
|
return errAuthFailed
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func makeAuthInfo(query url.Values) AuthInfo {
|
|
return AuthInfo{
|
|
Secret: query.Get("secret"),
|
|
}
|
|
}
|
|
|
|
func authenticate(authInfo AuthInfo, requiredRole Role) bool {
|
|
return requiredRole == RoleNone || authInfo.Secret == apiSecret
|
|
}
|
|
|
|
func replyError(w http.ResponseWriter, statusCode int, errMsg APIErrorString) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(statusCode)
|
|
|
|
jsonData := []byte(`{"error":"` + string(errMsg) + `"}`)
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
|
_, _ = w.Write(jsonData)
|
|
}
|
|
|
|
func replyOK(w http.ResponseWriter, data any) {
|
|
if data == nil {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
jsonData, err := json.Marshal(data)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
|
_, _ = w.Write(jsonData)
|
|
}
|