diff --git a/Config/readme.md b/Config/readme.md new file mode 100644 index 0000000..61543d9 --- /dev/null +++ b/Config/readme.md @@ -0,0 +1,25 @@ +### How to use this folder + +Welcome to the config folder, in this folder you can config your chat server with the Config cmds. + +Be notice that the main Config file for your server is .env file you can find a sample .env file in the root of this project + +For Create your configuration file, just make a `config.json` file here + +### CMDS (JSON) + +_ServerName -> information: your server name
+Server_Description -> information: your server description
+Server_Owner -> information: your server manager
+Server_Date_Format -> Full Date format or simple Date Format_ + +### Example + +```json +{ + "Server_Name": "Sunsend1", + "Server_Description": "This is the Default Server", + "Server_Owner": "SunSend", + "Sever_Date_Format": "full" // full/normal/simple +} +``` diff --git a/Config/words.l b/Config/words.l new file mode 100644 index 0000000..3220089 --- /dev/null +++ b/Config/words.l @@ -0,0 +1 @@ +nice \ No newline at end of file diff --git a/Makefile b/Makefile index 4dc0565..58259af 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +setup: + go run cmd/gen_key.go + run: go run cmd/api/main.go @@ -7,4 +10,9 @@ build: doc: touch doc echo "welcome" > doc - echo "Done." \ No newline at end of file + echo "Done." + +config: + mkdir -p Config/ + touch Config/config.json + echo "Done."" \ No newline at end of file diff --git a/Storage/SunSend.db b/Storage/SunSend.db deleted file mode 100644 index 6938917..0000000 Binary files a/Storage/SunSend.db and /dev/null differ diff --git a/TODO.md b/TODO.md index 7266c36..85f3abd 100644 --- a/TODO.md +++ b/TODO.md @@ -4,3 +4,8 @@ - [ ] Convert Error Numbers to a model - [x] Add Create Channel - [x] Add Database to save messages +- [ ] Add Server Route for more information about server +- [ ] Add managment panel for config server +- [ ] User Scripts for making small plugins to expand the server capability +- [ ] Add API key feature for servers +- [ ] Make a ConfigCore to manage config files diff --git a/cmd/api/main.go b/cmd/api/main.go index 3380ff6..8f6c591 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -1,23 +1,21 @@ package main import ( + "fmt" "log" - "os" + "sunsend/internals/CoreConfig" "sunsend/internals/DB" "sunsend/internals/Handlers" "sunsend/internals/Renderer" "text/template" - "github.com/joho/godotenv" "github.com/labstack/echo/v4" ) func main() { e := echo.New() - err := godotenv.Load(".env") - if err != nil { - log.Fatal(err.Error()) - } + CoreConfig.UpdateConfigs() // load bouth .env configs and user configs + log.Println("Your API Key is: " + fmt.Sprintf("%s", CoreConfig.Configs.Server.Key)) // e.Use(middleware.Logger()) // e.Use(middleware.Recover()) e.Renderer = &Renderer.Template{ @@ -26,5 +24,5 @@ func main() { Handlers.Handler(e) DB.PrepairDBSystem() - e.Logger.Fatal(e.Start(":" + os.Getenv("PORT"))) + e.Logger.Fatal(e.Start(":" + CoreConfig.Configs.Dotenv["PORT"])) } diff --git a/cmd/gen_key.go b/cmd/gen_key.go new file mode 100644 index 0000000..2fa6c93 --- /dev/null +++ b/cmd/gen_key.go @@ -0,0 +1,18 @@ +package main + +import ( + "crypto/sha256" + "fmt" + + "github.com/thanhpk/randstr" +) + +func GenerateAPIKey() [32]byte { + token := randstr.Hex(16) // generate 128-bit hex string + return sha256.Sum256([]byte(token)) +} + +func main() { + res := GenerateAPIKey() + fmt.Println("Your API KEY is: ", fmt.Sprintf("%X", res)) +} diff --git a/go.mod b/go.mod index c153f0d..2bb2b02 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/thanhpk/randstr v1.0.6 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.17.0 // indirect diff --git a/go.sum b/go.sum index 37cdc78..97780d1 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o= +github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/internals/Base/B_channel.go b/internals/Base/BaseChannel.go similarity index 100% rename from internals/Base/B_channel.go rename to internals/Base/BaseChannel.go diff --git a/internals/Base/Messages.go b/internals/Base/Messages.go index ecfdff5..8e1f1c0 100644 --- a/internals/Base/Messages.go +++ b/internals/Base/Messages.go @@ -3,19 +3,25 @@ package Base import ( "fmt" "log" + "strings" "sunsend/internals/DB" "sunsend/internals/Data" ) func IsEqulTo(message string) bool { - return message == "321" + for _, v := range *Data.LoadedWordList { + if message == v || strings.Contains(message, v) { + return true + } + } + return false } func CheckMessage(message string) int { if len(message) > 30 { - return 15 + return 15 // response 15 -> length error } else if IsEqulTo(message) { - return 12 + return 12 // response 12 -> Word error } return 0 } diff --git a/internals/Base/key.go b/internals/Base/key.go new file mode 100644 index 0000000..3a87054 --- /dev/null +++ b/internals/Base/key.go @@ -0,0 +1,40 @@ +package Base + +import ( + "crypto/sha256" + "crypto/subtle" + "fmt" + "strings" + "sunsend/internals/CoreConfig" +) + +// Doc: https://dev.to/caiorcferreira/implementing-a-safe-and-sound-api-key-authorization-middleware-in-go-3g2c + +func GenerateAPIKey(rawkey string) [32]byte { + // token := randstr.Hex(16) // generate 128-bit hex string + return sha256.Sum256([]byte(rawkey)) +} + +func BearerToken(headers map[string][]string) (string, int) { + fmt.Println(headers) + api_key_org, ok := headers["Api_key"] + if !ok { + return "", 17 // invalid API KE Y + } + if len(strings.SplitN(api_key_org[0], " ", 2)) > 2 { + return "", 17 + } + return strings.TrimSpace(api_key_org[0]), 0 + +} + +// apiKeyIsValid checks if the given API key is valid and returns the principal if it is. +func ApiKeyIsValid(user_api_key string) int { + // hash := sha256.Sum256([]byte(user_api_key)) + // key := hash[:] + if subtle.ConstantTimeCompare([]byte(CoreConfig.Configs.Server.Key), []byte(user_api_key)) == 1 { + return 0 + } + + return 17 +} diff --git a/internals/CoreConfig/ConfigReader.go b/internals/CoreConfig/ConfigReader.go new file mode 100644 index 0000000..c264423 --- /dev/null +++ b/internals/CoreConfig/ConfigReader.go @@ -0,0 +1,48 @@ +package CoreConfig + +import ( + "errors" + "log" + "os" + "sunsend/internals/Data" + + "github.com/joho/godotenv" +) + +var Configs *Data.Config +var rawapikey string + +func getEnvConfig() (map[string]string, error) { + err := godotenv.Load(".env") + if err != nil { + // log.Fatal(err.Error()) + return nil, errors.New("invalid or can't read .env file") + } + // It can be better to list of all the needed Config and iterate thought them + ret_values := make(map[string]string) + ret_values["PORT"] = os.Getenv("PORT") + ret_values["KEY"] = os.Getenv("KEY") + // copy(rawapikey[:], ret_values["KEY"]) // fix fixing some invalid memory address - TODO: Fix in better way - nice + rawapikey = ret_values["KEY"] + return ret_values, nil +} + +func UpdateConfigs() { + envconfigs, err := getEnvConfig() + if err != nil { + log.Fatal(err.Error()) + } + // TODO: get the user configs here + + Configs = &Data.Config{ + Dotenv: envconfigs, + Uconfig: nil, // for now just a little cute nil ^^ + Server: &Data.Server{ // TODO: will holds data from user config file + Name: "test", + Description: "test1", + Owner: "test", + Date: "test", + Key: rawapikey, + }, + } +} diff --git a/internals/DB/db_structure.go b/internals/DB/db_structure.go index 2bd3882..c6ff1b4 100644 --- a/internals/DB/db_structure.go +++ b/internals/DB/db_structure.go @@ -19,7 +19,7 @@ func createBaseTable() int { db_work := getbasedb() base_channels_table := `CREATE TABLE Channels ( - "ID" integer NOT NULL PRIMARY KEY, + "ID" integer NOT NULL PRIMARY KEY, "Name" TEXT, "Description" TEXT, "Owner" TEXT @@ -31,7 +31,12 @@ func createBaseTable() int { fmt.Println("this is where I stoped the program hahahahaha") log.Fatalln(err.Error()) } - statement_channel.Exec() // Execute SQL Statements + res, err := statement_channel.Exec() // Execute SQL Statements + if err != nil { + // fmt.Println(err.Error(), res) + log.Fatal(err.Error(), res) + } + log.Println("Base Channels table created") // CreateMessageTable() diff --git a/internals/Data/Response.go b/internals/Data/Response.go index 4c00d6d..d1a4270 100644 --- a/internals/Data/Response.go +++ b/internals/Data/Response.go @@ -28,6 +28,8 @@ func GetErrorByResult(res_code int) (string, int, string) { return "Your Messages Length is more than 30 character", http.StatusBadRequest, "FAILD" // response Status Error - message length case 16: return "There is a problem in the system", http.StatusServiceUnavailable, "FAILD" // response Status Error - Server has problem (bug) + case 17: + return "Invalid API KEY", http.StatusBadRequest, "FAILD" // response Status Error - invalid api key default: return "There is a problem", http.StatusNotAcceptable, "FAILD" // Response Status Error - uknown Eror } diff --git a/internals/Data/config_struct.go b/internals/Data/config_struct.go new file mode 100644 index 0000000..bd8a398 --- /dev/null +++ b/internals/Data/config_struct.go @@ -0,0 +1,7 @@ +package Data + +type Config struct { + Dotenv map[string]string // .env file configs + Uconfig map[string]string // usr `Config` folder configs + Server *Server +} diff --git a/internals/Data/server_struct.go b/internals/Data/server_struct.go new file mode 100644 index 0000000..47fbe66 --- /dev/null +++ b/internals/Data/server_struct.go @@ -0,0 +1,9 @@ +package Data + +type Server struct { + Name string + Description string + Owner string + Date string + Key string +} diff --git a/internals/Data/words.go b/internals/Data/words.go new file mode 100644 index 0000000..b6906b5 --- /dev/null +++ b/internals/Data/words.go @@ -0,0 +1,25 @@ +package Data + +/* + This file will holds the words that shouldn't be on chat or send as a data to the server + It is better to not change edit this file and instead make a words list on config/words.l + There words are default for each server, and you should not remove these. +*/ + +var LoadedWordList *[]string + +var defaultWords []string = []string{ + "321", + "nice", + "yes", +} + +func LoadWordsFromConfig() { + // Load Words From Config File + LoadedWordList = &[]string{"config"} + *LoadedWordList = append(*LoadedWordList, defaultWords...) +} + +func GetAllWords() *[]string { + return LoadedWordList +} diff --git a/internals/Routes/chat.go b/internals/Routes/chat.go index b659e6a..0cd1299 100644 --- a/internals/Routes/chat.go +++ b/internals/Routes/chat.go @@ -22,12 +22,14 @@ func GetChatPostAction(c echo.Context) error { err := Base.CheckMessage(msg) if err != 0 { response, _ := Data.NewResponse(c, err, channel_id_user, nil) - return c.JSON(http.StatusBadRequest, response) // should be same Statuscode as NewResponse + _, error_code_org, _ := Data.GetErrorByResult(err) + return c.JSON(error_code_org, response) // should be same Statuscode as NewResponse } res_exists_channel := Base.ChannelExists(channel_id_user) if res_exists_channel != 0 { response_org, _ := Data.NewResponse(c, res_exists_channel, channel_id_user, nil) - return c.JSON(http.StatusBadRequest, response_org) // should be same Statuscode as NewResponse + _, error_code_org2, _ := Data.GetErrorByResult(res_exists_channel) + return c.JSON(error_code_org2, response_org) // should be same Statuscode as NewResponse } // fmt.Println(msg) @@ -35,14 +37,16 @@ func GetChatPostAction(c echo.Context) error { res := DB.InsertMsg(IChannel_id_ser, rand.Intn(999), user, msg, time.Now().String(), 0) if res != 0 { response, _ := Data.NewResponse(c, res, channel_id_user, nil) - return c.JSON(http.StatusBadRequest, response) + _, error_code_org3, _ := Data.GetErrorByResult(res_exists_channel) + return c.JSON(error_code_org3, response) } response, _ := Data.NewResponse(c, 0, channel_id_user, msg) - return c.JSON(http.StatusOK, response) + _, error_code_org4, _ := Data.GetErrorByResult(res_exists_channel) + return c.JSON(error_code_org4, response) } -func StreamResponseJSON(c echo.Context, chat_data []*Data.Chat) error { +func StreamResponseJSON(c echo.Context, chat_data *Data.Response) error { c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) c.Response().WriteHeader(http.StatusOK) return json.NewEncoder(c.Response()).Encode(chat_data) @@ -70,10 +74,23 @@ func chatActionFunc(c echo.Context) error { } fmt.Println(len(chat_collection)) - - // response, _ = Data.NewResponse(c, res, channel_id, chat_collection) + headers := c.Request().Header + apiKey, res_api_key := Base.BearerToken(headers) + if res != 0 { + response, _ = Data.NewResponse(c, res_api_key, channel_id, nil) + _, error_code_org, _ := Data.GetErrorByResult(res_api_key) + return c.JSON(error_code_org, response) + } + res_check_api := Base.ApiKeyIsValid(apiKey) + if res_check_api != 0 { + response, _ = Data.NewResponse(c, res_check_api, channel_id, nil) + _, error_code_org, _ := Data.GetErrorByResult(res_check_api) + return c.JSON(error_code_org, response) + } + fmt.Println("API KEY:", apiKey, "requested to server succsessfully") + response, _ = Data.NewResponse(c, res, channel_id, chat_collection) // return c.JSON(http.StatusOK, response) - return StreamResponseJSON(c, chat_collection) // Stream JSON File + return StreamResponseJSON(c, response) // Stream JSON File // return c.JSON(http.StatusAccepted, map[string]interface{}{ // "asd": "nice", // })