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) }