diff --git a/.gitignore b/.gitignore index 1a506f1..8135020 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ # vendor/ .idea secrets.json +test +secrets_test.json diff --git a/config.json b/config.json index 23e7ed3..f33ebaa 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,6 @@ { "createGiteaAccount": false, "port": 8080, - "rootUrl": "http://localhost:8080" + "rootUrl": "http://localhost:8080", + "databaseType": "mysql" } diff --git a/config_test.json b/config_test.json new file mode 100644 index 0000000..ccb440e --- /dev/null +++ b/config_test.json @@ -0,0 +1,6 @@ +{ + "createGiteaAccount": false, + "port": 8080, + "rootUrl": "http://localhost:8080", + "databaseType": "sqlite3" +} diff --git a/go.mod b/go.mod index c492b1e..d1511ad 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/go-sql-driver/mysql v1.5.0 github.com/golang/protobuf v1.4.3 // indirect github.com/gorilla/websocket v1.4.2 // indirect + github.com/mattn/go-sqlite3 v1.14.6 github.com/zaddok/moodle v0.6.6 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect diff --git a/go.sum b/go.sum index 62aa8a3..527e988 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/src/login.go b/src/login.go index 9fc634d..2d0a592 100644 --- a/src/login.go +++ b/src/login.go @@ -14,7 +14,7 @@ const sessionTimeout time.Duration = 10 * 24 * time.Hour func login(w http.ResponseWriter, r *http.Request) { var redirectUrl = r.FormValue("redirecturl") if redirectUrl == "" { - redirectUrl = "dash" + redirectUrl = "/" } loginStruct := loginStruct{} var login bool = false @@ -36,7 +36,6 @@ func login(w http.ResponseWriter, r *http.Request) { cookie := http.Cookie{ Name: sessionName, Value: key, - Domain: "redstoneunion.de", Expires: time.Now().Add(sessionTimeout), HttpOnly: true, Secure: true, diff --git a/src/main.go b/src/main.go index f3cc049..545e93d 100644 --- a/src/main.go +++ b/src/main.go @@ -7,6 +7,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/dlclark/regexp2" _ "github.com/go-sql-driver/mysql" + _ "github.com/mattn/go-sqlite3" "github.com/zaddok/moodle" "html/template" "io/ioutil" @@ -24,6 +25,7 @@ var registerTmpl *template.Template var submitTmpl *template.Template var loginTmpl *template.Template var stmtCreateAccount *sql.Stmt +var isTest bool type secrets_json struct { DiscordToken string `json:"discordToken"` MysqlIndentify string `json:"mysqlIndentify"` @@ -31,17 +33,22 @@ type secrets_json struct { MoodleToken string `json:"moodleToken"` GiteaToken string `json:"giteaToken"` ApiToken string `json:"apiToken"` + DiscordTestUser string `json:"discordTestUser"` + DiscordTestUserEmail string `json:"discordTestUserEmail"` + DiscordTestUserPassword string `json:"discordTestUserpassword"` + DiscordBotUserId string `json:"discordBotUserId"` } type config_json struct { CreateGiteaAccount bool `json:"createGiteaAccount"` Port uint16 `json:"port"` RootUrl string `json:"rootUrl"` + DatabaseType string `json:"databaseType"` } func main() { var err error var jsonfile *os.File - jsonfile, err = os.Open("secrets.json") + jsonfile, err = os.Open("secrets" + testFilename() + ".json") log(err) var jsondata []byte jsondata, err = ioutil.ReadAll(jsonfile) @@ -49,19 +56,23 @@ func main() { err = json.Unmarshal(jsondata, &secret) log(err) jsonfile.Close() - jsonfile, err = os.Open("config.json") + jsonfile, err = os.Open("config" + testFilename() + ".json") log(err) jsondata, err = ioutil.ReadAll(jsonfile) log(err) err = json.Unmarshal(jsondata, &config) log(err) jsonfile.Close() + if(config.DatabaseType != "mysql" && config.DatabaseType != "sqlite3") { + fmt.Println("Unknown database type. Use mysql or sqlite3") + os.Exit(1) + } discordgo.MakeIntent(discordgo.IntentsAll) discord, err = discordgo.New("Bot " + secret.DiscordToken) log(err) err = discord.Open() log(err) - db, err = sql.Open("mysql", secret.MysqlIndentify) + db, err = sql.Open(config.DatabaseType, secret.MysqlIndentify) log(err) _, err = db.Exec("CREATE TABLE IF NOT EXISTS account(" + "username varchar(40) NOT NULL, " + @@ -88,5 +99,14 @@ func main() { http.HandleFunc("/login", login) http.HandleFunc("/api/accountinfo", accountApi) - http.ListenAndServe(":" + fmt.Sprint(config.Port), nil) + if(!isTest) { + http.ListenAndServe(":" + fmt.Sprint(config.Port), nil) + } +} + +func testFilename() string { + if(isTest) { + return "_test" + } + return "" } diff --git a/src/main_test.go b/src/main_test.go new file mode 100644 index 0000000..63c9b23 --- /dev/null +++ b/src/main_test.go @@ -0,0 +1,92 @@ +package main + +import ( + "testing" + "net/http" + "net/http/httptest" + "net/url" + "bytes" + "strings" + "github.com/bwmarrin/discordgo" + "github.com/dlclark/regexp2" + "html/template" + "io" +) + +var testPassword = "*#566jgjgJJf" +var testUsername = "ausername" +var testSession string + +func TestOrder(test *testing.T) { + isTest = true + main() + test.Run("register", testRegister) + test.Run("submit", testSubmit) + test.Run("login", testLogin) +} + +func testSetUsernamePassword(form url.Values) { + form.Set("username", testUsername) + form.Set("password", testPassword) +} + +func testForm(test *testing.T, url string, statusCode int, handler http.HandlerFunc, form url.Values) *http.Response { + request := httptest.NewRequest("POST", url, strings.NewReader(form.Encode())) + request.Form = form + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, request) + if recorder.Code != statusCode { + test.Errorf("handler returned wrong status code: got %v want %v", recorder.Code, http.StatusOK) + } + return recorder.Result() +} + +func checkBodyByTemplate(test *testing.T, response *http.Response, template *template.Template, templateData interface{}) { + var expectedResponse bytes.Buffer + template.Execute(&expectedResponse, templateData) + checkBody(test, response, expectedResponse.Bytes()) +} + +func checkBody(test *testing.T, response *http.Response, expectedResponse []byte) { + responseBody, _ := io.ReadAll(response.Body) + if bytes.Equal(expectedResponse, responseBody) { + test.Errorf("unexpected body:\n%v", string(responseBody)) + } +} + +func testRegister(test *testing.T) { + form := url.Values{} + testSetUsernamePassword(form) + form.Set("email", "jffg@fv.com") + form.Set("discordUser", secret.DiscordTestUser) + response := testForm(test, "/register", http.StatusOK, register, form) + checkBodyByTemplate(test, response, registerTmpl, registerStruct{Success: true}) +} + +func testSubmit(test *testing.T) { + discordClient, err := discordgo.New() + log(err) + err = discordClient.Login(secret.DiscordTestUserEmail, secret.DiscordTestUserPassword) + log(err) + err = discordClient.Open() + log(err) + channel, err := discordClient.UserChannelCreate(secret.DiscordBotUserId) + log(err) + msg, err := discordClient.ChannelMessages(channel.ID, 1, "", "", channel.LastMessageID) + log(err) + re := regexp2.MustCompile(`(?<=\?token\=)[^>]*`, 0) + match, _ := re.FindStringMatch(msg[0].Content) + if match == nil { + test.Error("The submit link was not send") + } + form := url.Values{} + form.Set("token", match.String()) + response := testForm(test, "/submit", http.StatusOK, submit, form) + checkBodyByTemplate(test, response, submitTmpl, submitStruct{Success: true}) +} + +func testLogin(test *testing.T) { + form := url.Values{} + testSetUsernamePassword(form) + testForm(test, "/login", http.StatusSeeOther, login, form) +} diff --git a/src/register.go b/src/register.go index 8e94bc9..7aca89d 100644 --- a/src/register.go +++ b/src/register.go @@ -24,7 +24,7 @@ type WrongAccount struct { Email bool DiscordUser bool } -type registertmpl struct { +type registerStruct struct { Success bool WrongAccount WrongAccount AlreadyEsitsInDatabase struct{ @@ -32,7 +32,7 @@ type registertmpl struct { DiscordUsername bool } } -type SubmitStruct struct { +type submitStruct struct { Success bool } var cacheAccounts hashmap.HashMap @@ -40,7 +40,7 @@ var rusername *regexp.Regexp var remail *regexp2.Regexp var rpassword *regexp2.Regexp func register(w http.ResponseWriter, r *http.Request) { - registerstruct := registertmpl{} + registerStruct := registerStruct{} if r.Method == http.MethodPost { var newAccount account var newRbuMember *discordgo.Member @@ -54,24 +54,24 @@ func register(w http.ResponseWriter, r *http.Request) { newAccount.discordUsername = split[0] newAccount.discordTag = split[1] } - registerstruct.WrongAccount.Email, _ = remail.MatchString(newAccount.email) - registerstruct.WrongAccount.Email = !registerstruct.WrongAccount.Email - registerstruct.WrongAccount.User = !rusername.MatchString(newAccount.username) - registerstruct.WrongAccount.Pass, _ = rpassword.MatchString(newAccount.password) - registerstruct.WrongAccount.Pass = !registerstruct.WrongAccount.Pass - newRbuMember, registerstruct.WrongAccount.DiscordUser = getRbuMember(newAccount.discordUsername, newAccount.discordTag) - registerstruct.WrongAccount.DiscordUser = !registerstruct.WrongAccount.DiscordUser - if registerstruct.WrongAccount.DiscordUser { + registerStruct.WrongAccount.Email, _ = remail.MatchString(newAccount.email) + registerStruct.WrongAccount.Email = !registerStruct.WrongAccount.Email + registerStruct.WrongAccount.User = !rusername.MatchString(newAccount.username) + registerStruct.WrongAccount.Pass, _ = rpassword.MatchString(newAccount.password) + registerStruct.WrongAccount.Pass = !registerStruct.WrongAccount.Pass + newRbuMember, registerStruct.WrongAccount.DiscordUser = getRbuMember(newAccount.discordUsername, newAccount.discordTag) + registerStruct.WrongAccount.DiscordUser = !registerStruct.WrongAccount.DiscordUser + if registerStruct.WrongAccount.DiscordUser { goto registerReturn } newAccount.discordId = newRbuMember.User.ID { var username string - registerstruct.AlreadyEsitsInDatabase.Username = db.QueryRow("SELECT username FROM account WHERE username = ?", newAccount.username).Scan(&username) == nil || UsernameExistsInMem(newAccount.username) // check if username exits - registerstruct.AlreadyEsitsInDatabase.DiscordUsername = db.QueryRow("SELECT username FROM account WHERE discordUserId = ?", newAccount.discordId).Scan(&username) == nil || discordUsernameExistsInMem(newAccount.discordId) + registerStruct.AlreadyEsitsInDatabase.Username = db.QueryRow("SELECT username FROM account WHERE username = ?", newAccount.username).Scan(&username) == nil || UsernameExistsInMem(newAccount.username) // check if username exits + registerStruct.AlreadyEsitsInDatabase.DiscordUsername = db.QueryRow("SELECT username FROM account WHERE discordUserId = ?", newAccount.discordId).Scan(&username) == nil || discordUsernameExistsInMem(newAccount.discordId) } - registerstruct.Success = !registerstruct.WrongAccount.User && !registerstruct.WrongAccount.Pass && !registerstruct.WrongAccount.Email && !registerstruct.WrongAccount.DiscordUser && !registerstruct.AlreadyEsitsInDatabase.DiscordUsername && !registerstruct.AlreadyEsitsInDatabase.Username - if !registerstruct.Success { + registerStruct.Success = !registerStruct.WrongAccount.User && !registerStruct.WrongAccount.Pass && !registerStruct.WrongAccount.Email && !registerStruct.WrongAccount.DiscordUser && !registerStruct.AlreadyEsitsInDatabase.DiscordUsername && !registerStruct.AlreadyEsitsInDatabase.Username + if !registerStruct.Success { goto registerReturn } token, err := GenerateRandomStringURLSafe(64) @@ -82,11 +82,11 @@ func register(w http.ResponseWriter, r *http.Request) { discord.ChannelMessageSend(dmChannel.ID, "Bitte klicke auf den Link, um die Erstellung des Accounts abzuschließen.\n<" + config.RootUrl + "/submit?token=" + token + ">") cacheAccounts.Set(token, newAccount) } - registerReturn: runTemplate(w, registerTmpl, registerstruct) + registerReturn: runTemplate(w, registerTmpl, registerStruct) } func submit(w http.ResponseWriter, r *http.Request) { var err error - var submitStruct SubmitStruct + var submitStruct submitStruct token := r.FormValue("token") var accInter interface{} accInter, submitStruct.Success = cacheAccounts.GetStringKey(token)