2022-09-26 22:38:13 +02:00

203 lines
6.2 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/FrankenBotDev/FrankenAPI/ent/authorizables"
"github.com/FrankenBotDev/FrankenAPI/pkg/routes/actions"
"github.com/FrankenBotDev/FrankenAPI/pkg/routes/logger"
"github.com/FrankenBotDev/FrankenAPI/pkg/routes/punishments"
"github.com/FrankenBotDev/FrankenAPI/pkg/security"
"github.com/FrankenBotDev/FrankenAPI/pkg/routes/settings"
"github.com/FrankenBotDev/FrankenAPI/pkg/routes/stats"
"github.com/FrankenBotDev/FrankenAPI/pkg/routes/support"
"github.com/FrankenBotDev/FrankenAPI/pkg/sys"
"github.com/joho/godotenv"
"gopkg.in/olahol/melody.v1"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/jwtauth/v5"
_ "github.com/jackc/pgx/v4/stdlib"
)
type LoginRequest struct {
Username string `json:username`
Password string `json:password`
}
type GopherInfo struct {
ID, USERID, CH string
}
type Message struct {
Command string
Payload map[string]string
}
func main() {
mrouter := melody.New()
gophers := make(map[*melody.Session]*GopherInfo)
counter := 0
err := godotenv.Load(".env")
if err != nil {
fmt.Println(".end could not be loaded")
log.Fatal(err)
}
client, cContext := sys.InitDB()
tokenAuth := jwtauth.New("HS256", []byte("secret"), nil)
defer client.Close()
r := chi.NewRouter()
r.Use(middleware.Logger)
// Basic CORS
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
r.Use(cors.Handler(cors.Options{
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
AllowedOrigins: []string{"https://*", "http://*"},
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: false,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}))
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), sys.EntClientKey, client)
next.ServeHTTP(w, r.WithContext(ctx))
})
})
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), sys.ContextClientKey, cContext)
next.ServeHTTP(w, r.WithContext(ctx))
})
})
// Witout JWT
r.Group(func(r chi.Router) {
// AUTHENTIFICATION ROUTER
// using loging and password from a user to authenticate
// Returns bearer if login is succefful and error if not
r.Post("/server/auth", func(w http.ResponseWriter, r *http.Request) {
var loginRequest LoginRequest
err := json.NewDecoder(r.Body).Decode(&loginRequest)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Queries requested data
user, err := client.Authorizables.Query().
Where(authorizables.Username(loginRequest.Username), authorizables.Password(security.NewSHA256asString([]byte(loginRequest.Password)))).
All(cContext)
if err != nil {
http.Error(w, "database error", http.StatusInternalServerError)
}
// Validates user entry if one is present login can proceed
if len(user) == 1 {
_, tokenString, err := tokenAuth.Encode(map[string]interface{}{"user_id": 1})
if err != nil {
sys.ErrorJsonResponse(w, "encoding_error", err)
}
raw, _ := json.Marshal(`{"type":"success", "token":"` + tokenString + `"}`)
w.Write(raw)
return
} else {
http.Error(w, `{"type":"error", "code":"invalid credentials"}`, http.StatusUnauthorized)
return
}
})
})
// With JWT
r.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(tokenAuth))
r.Use(jwtauth.Authenticator)
r.Mount("/logger", logger.LogRouter())
r.Mount("/server/punishments", punishments.PunRouter())
r.Mount("/server/settings", settings.SettingsRouter())
r.Mount("/server/actions", actions.ActionRouter())
r.Mount("/server/support", support.SupportRouter())
r.Mount("/stats", stats.StatsRouter())
})
/* ==================================================
WEBSOCKET CODE
================================================== */
// Websocket Endpoint
r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("websocket connection")
mrouter.HandleRequest(w, r)
})
// Connect Socket
mrouter.HandleConnect(func(s *melody.Session) {
fmt.Println("websocket connecting")
// Receives data and assignes it to the gophers map
gophers[s] = &GopherInfo{strconv.Itoa(counter), s.Request.URL.Query().Get("userid"), s.Request.URL.Query().Get("channel")}
// Message user client about Successfull Connection and tell the User
//s.Write([]byte(`{"type":"success", "id":` + gophers[s].ID + `}`))
s.Write([]byte(`{"comamnd":"msg", "payload":{"name":"SYSTEM", "userid":"system", "type":"INFO", "msg":"You are connected to:` + s.Request.URL.Query().Get("channel") + `"} }`))
counter += 1
})
// handle message and send to correct channel after session as created for user
mrouter.HandleMessage(func(s *melody.Session, msg []byte) {
info := gophers[s]
commandJson := Message{}
json.Unmarshal(msg, &commandJson)
// Command Handler
switch commandJson.Command {
case "msg":
raw, err := json.Marshal(`{"command":"msg", "payload":{"name":"` + commandJson.Payload["name"] + `", "userid":"` + commandJson.Payload["userid"] + `", "type":"default", "msg":"` + commandJson.Payload["msg"] + `"} }`)
if err != nil {
fmt.Println(err)
}
mrouter.BroadcastFilter(raw, func(q *melody.Session) bool {
// log messages here
return info.CH == q.Request.URL.Query().Get("channel")
})
}
})
// Deletes socket out of known Map
mrouter.HandleDisconnect(func(s *melody.Session) {
tCH := gophers[s].CH
// Sends a message to the channel that a user left
mrouter.BroadcastFilter([]byte(gophers[s].USERID+"left the channel"), func(q *melody.Session) bool {
// log messages here
return gophers[s].CH == tCH
})
// Deletes a user from the internal client map
delete(gophers, s)
})
fmt.Println("API Online on 0.0.0.0:3000")
http.ListenAndServe("0.0.0.0:3000", r)
}