From e31b06b7b56aaf1cf5510d4bb0261f73bc0e8893 Mon Sep 17 00:00:00 2001 From: Horus_Arch Date: Mon, 2 Mar 2015 15:31:39 +0100 Subject: Migrating from revel. --- app/controllers/app.go | 195 ----------------------- app/controllers/db.go | 119 -------------- app/controllers/mail.go | 45 ------ app/controllers/utilities.go | 94 ------------ app/db.go | 39 +++++ app/handler.go | 247 ++++++++++++++++++++++++++++++ app/init.go | 43 ------ app/main.go | 8 + app/struct.go | 92 +++++++++++ app/utilities.go | 115 ++++++++++++++ app/utilities_test.go | 106 +++++++++++++ app/views/App/Index.html | 23 --- app/views/Mailer/ConfirmRegistration.html | 5 - app/views/Mailer/SendConfirmationKey.html | 4 - app/views/debug.html | 64 -------- app/views/errors/404.html | 20 --- app/views/errors/500.html | 16 -- app/views/flash.html | 18 --- app/views/footer.html | 5 - app/views/header.html | 17 -- 20 files changed, 607 insertions(+), 668 deletions(-) delete mode 100644 app/controllers/app.go delete mode 100644 app/controllers/db.go delete mode 100644 app/controllers/mail.go delete mode 100644 app/controllers/utilities.go create mode 100644 app/db.go create mode 100644 app/handler.go delete mode 100644 app/init.go create mode 100644 app/main.go create mode 100644 app/struct.go create mode 100644 app/utilities.go create mode 100644 app/utilities_test.go delete mode 100644 app/views/App/Index.html delete mode 100644 app/views/Mailer/ConfirmRegistration.html delete mode 100644 app/views/Mailer/SendConfirmationKey.html delete mode 100644 app/views/debug.html delete mode 100644 app/views/errors/404.html delete mode 100644 app/views/errors/500.html delete mode 100644 app/views/flash.html delete mode 100644 app/views/footer.html delete mode 100644 app/views/header.html (limited to 'app') diff --git a/app/controllers/app.go b/app/controllers/app.go deleted file mode 100644 index 9e5764f..0000000 --- a/app/controllers/app.go +++ /dev/null @@ -1,195 +0,0 @@ -package controllers - -import ( - "github.com/jinzhu/gorm" - "github.com/revel/revel" - "github.com/revel/revel/modules/jobs/app/jobs" -) - -type App struct { - *revel.Controller -} - -func (c App) Index() revel.Result { - return c.Render() -} - -func (c App) PrintLogin(legacy bool) revel.Result { - return c.Render(legacy) -} - -func (c App) PrintRegister() revel.Result { - return c.Render() -} - -func (c App) Account() revel.Result { - return c.Render() -} - -func (c App) Login(email string, legacy bool, user string, password string, passwordConfirm string) revel.Result { - - if legacy { - // Show login form with username and password - c.Validation.Required(user).Message("Please enter a user name.") - c.Validation.Required(password).Message("Please enter a password.") - c.Validation.Required(passwordConfirm).Message("Please confirm your password.") - c.Validation.Required(password == passwordConfirm).Message("The passwords do not match.") - } else { - // Show login form only with email - c.Validation.Required(email).Message("Please provide a mail adress.") - } - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(App.PrintLogin) - } - - u := User{} - if legacy { - // do database lookup and show if its matched - db.Where("name = ?", user).First(&u) - c.Validation.Required(u.Confirmed).Message("Your mail adress is not confirmed yet.") - verify, _ := VerifyPassword(password, u.Password) - c.Validation.Required(verify).Message("The user/password combination does not exists.") - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(App.PrintLogin) - } - - c.Session["login"] = "true" - c.Session["uid"] = string(u.Id) - - return c.Redirect(App.Account) - } else { - if db.Where("email = ?", email).First(&u).Error == gorm.RecordNotFound { - // Invalid Email - c.Flash.Error("No valid mail adress.") - return c.Redirect(App.PrintLogin) - } - // Get random string - key := RandomKey() - // Set key in redis - conn := pool.Get() - defer conn.Close() - _, _ = conn.Do("SET", key, u.Email, 86400) - // Send email with confirmation link - //jobs.Now(Mailer{}.SendConfirmationKey(email, key)) - _ = Mailer{}.SendConfirmationKey(email, key) - - c.Flash.Success("A mail with a confirmation link was sent. Follow the instructions there.") - - return c.Redirect(App.PrintLogin) - } -} - -func (c App) Confirm(key, registration string) revel.Result { - - if registration == "" { - // Processing login - - c.Validation.Required(key).Message("No key provided.") - - conn := pool.Get() - confirmKey, err := conn.Do("GET", key) - c.Validation.Required(err == nil).Message("Oops, there is currently an internal problem. Please check later again.") - c.Validation.Required(confirmKey).Message("Key does not seem to be valid.") - - _, _ = conn.Do("DEL", key) - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(App.PrintLogin) - } - - u := User{} - db.Where("email = ?", confirmKey).First(&u) - - if u.Confirmed == false { - // E-Mail is now confirmed - u.Confirmed = true - u.ConfirmationKey = "" - db.Save(&u) - } - - c.Session["login"] = "true" - c.Session["uid"] = string(u.Id) - - } else { - // Processing registration confirmation - - c.Validation.Required(registration).Message("No confirmation key provided.") - - u := User{} - db.Where("confirmationkey = ?").First(&u) - - c.Validation.Required(registration == u.ConfirmationKey).Message("Key does not seem to be valid.") - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(App.PrintLogin) - } - - u.Confirmed = true - u.ConfirmationKey = "" - db.Save(&u) - - c.Session["login"] = "true" - c.Session["uid"] = string(u.Id) - - } - - return c.Redirect(App.Account) -} - -func (c App) Register(email, confirmEmail, user, password, confirmPassword string) revel.Result { - - c.Validation.Required(email).Message("Please provide a mail adress.") - c.Validation.Required(email == confirmEmail).Message("The mail adresses do not match.") - c.Validation.Required(user).Message("Please provide a user name.") - - if password != "" { - c.Validation.Required(password == confirmPassword).Message("Passwords do not match.") - } - - if c.Validation.HasErrors() { - c.Validation.Keep() - c.FlashParams() - return c.Redirect(App.PrintRegister) - } - - p, _ := HashPassword(password) - key := RandomKey() // Create key to confirm mail adress - u := User{ - Name: user, - Email: email, - Password: p, - Confirmed: false, - ConfirmationKey: key, - Alerts: []Alert{{Email: email}}, - } - - db.NewRecord(u) - db.Create(&u) - db.Save(&u) - - // Send email with confirmation link - //jobs.Now(Mailer{}.ConfirmRegistration(email, key)) - //_ = Mailer{}.ConfirmRegistration(user, email, key) - jobs.Now(JobRegistration{User: user, Email: email, Key: key}) - c.Flash.Success("A mail with a confirmation link was sent. Please confirm your mail adress now.") - - return c.Redirect(App.PrintRegister) -} - -func (c App) Test(email, key string) revel.Result { - // jobs.Now(JobRegistration{User: "foobar", Email: email, Key: key}) - Mailer{}.ConfirmRegistration("foobar", "raspi@dns.iamfabulous.de", "string") - c.Flash.Success("A mail with a confirmation link was sent. Please confirm your mail adress now.") - - return c.Redirect(App.Index) -} diff --git a/app/controllers/db.go b/app/controllers/db.go deleted file mode 100644 index baa772c..0000000 --- a/app/controllers/db.go +++ /dev/null @@ -1,119 +0,0 @@ -package controllers - -import ( - "github.com/jinzhu/gorm" - _ "github.com/mattn/go-sqlite3" - "github.com/revel/revel" - "time" -) - -type User struct { - Id int64 - Email string `sql:"unique"` - Name string `sql:"unique"` - Password string - Confirmed bool - ConfirmationKey string - Alerts []Alert - CreatedAt time.Time - DeletedAt time.Time - UpdatedAt time.Time -} - -// Multiple accounts which are alerted -type Alert struct { - Id int64 - UserId int64 - Email string - CreatedAt time.Time - DeletedAt time.Time - UpdatedAt time.Time -} - -type Job struct { - Id int64 - UserId int64 - Name string - Url string - Versions []Version - Diff string - DiffLen int64 - CreatedAt time.Time - DeletedAt time.Time - UpdatedAt time.Time -} - -// Save history version of jobs -type Version struct { - Id int64 - JobId int64 - Content string - Hash string - CreatedAt time.Time - DeletedAt time.Time - UpdatedAt time.Time -} - -var db = DBInit() - -func DBInit() gorm.DB { - // Open database handler - // !!! FIXME THIS THROWS A PANIC AT COMPILE TIME. WHY? !!! - - //db, err := gorm.Open(revel.Config.StringDefault("db.driver", "sqlite3"), revel.Config.StringDefault("db.spec", "webmon.db")) - - /* OR */ - - /* - var ( - d, s string - ) - d = revel.Config.StringDefault("test.d", "sqlite3") - s = revel.Config.StringDefault("test.s", "webmon.db") - //db, err := gorm.Open(driver, spec) - - revel.WARN.Println(d) - revel.WARN.Println(s) - */ - - // This works. - db, err := gorm.Open("sqlite3", "webmon.db") - - // Set database logging to TRACE - db.LogMode(true) - db.SetLogger(gorm.Logger{revel.TRACE}) - - // Ping database to check if up - if err = db.DB().Ping(); err != nil { - revel.ERROR.Panicf("Failed to init database. %s \n", err) - } - defer db.Close() - - // Set Pool connections - db.DB().SetMaxIdleConns(10) - db.DB().SetMaxOpenConns(100) - - // Create tables which are defined as struct - db.Debug().CreateTable(&User{}) - db.Debug().CreateTable(&Alert{}) - db.Debug().CreateTable(&Job{}) - db.Debug().CreateTable(&Version{}) - - // Automigration - db.Debug().AutoMigrate(&User{}, &Job{}, &Version{}, &Alert{}) - - // Add index on email and name to enforce uniqueness - db.Debug().Model(&User{}).AddUniqueIndex("idx_user_email", "email") - db.Debug().Model(&User{}).AddUniqueIndex("idx_user_name", "name") - - // Unique index on alert to ensure every user can specify not multiple equally alerts - db.Debug().Model(&Alert{}).AddUniqueIndex("idx_alert_email_userid", "email", "user_id") - - // Unique index to ensure every user can have only one job with the same name - db.Debug().Model(&Job{}).AddUniqueIndex("idx_job_name_userid", "name", "user_id") - - // Makes hash unique - // db.Debug().Model(&Version{}).AddUniqueIndex("idx_version_hash", "hash") - - return db -} diff --git a/app/controllers/mail.go b/app/controllers/mail.go deleted file mode 100644 index 3fd2e94..0000000 --- a/app/controllers/mail.go +++ /dev/null @@ -1,45 +0,0 @@ -package controllers - -import ( - "github.com/tanema/revel_mailer" -) - -type Mailer struct { - revel_mailer.Mailer -} - -type JobConfirmationKey struct { - Email string - Key string -} - -func (j JobConfirmationKey) Run() { - _ = Mailer{}.SendConfirmationKey(j.Email, j.Key) -} - -func (u Mailer) SendConfirmationKey(email, key string) error { - return u.Send(revel_mailer.H{ - "subject": "Confirmation Key", - "to": []string{email}, - "key": key, - }) -} - -type JobRegistration struct { - User string - Email string - Key string -} - -func (j JobRegistration) Run() { - _ = Mailer{}.ConfirmRegistration(j.User, j.Email, j.Key) -} - -func (u Mailer) ConfirmRegistration(user, email, key string) error { - return u.Send(revel_mailer.H{ - "name": user, - "subject": "Confirm registration", - "to": []string{email}, - "key": key, - }) -} diff --git a/app/controllers/utilities.go b/app/controllers/utilities.go deleted file mode 100644 index 1aa5d6d..0000000 --- a/app/controllers/utilities.go +++ /dev/null @@ -1,94 +0,0 @@ -package controllers - -import ( - "crypto/md5" - "fmt" - "github.com/garyburd/redigo/redis" - "github.com/revel/revel" - "golang.org/x/crypto/bcrypt" - "io" - "io/ioutil" - "math/rand" - "net/http" - "time" -) - -// Returns the content of a webpage as string -func Get(url string) (string, error) { - response, err := http.Get(url) - if err != nil { - return "Get request failed.", err - } - - defer response.Body.Close() - contents, err := ioutil.ReadAll(response.Body) - if err != nil { - return "Reading body failed.", err - } - - return string(contents), nil -} - -// Hashs and returns a string (md5) -func Hash(content string) string { - h := md5.New() - io.WriteString(h, content) - hash := fmt.Sprintf("%x", h.Sum(nil)) - - return hash -} - -// Creates a random string -func RandomKey() string { - letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - key := make([]rune, 40) - for i := range key { - key[i] = letters[rand.Intn(len(letters))] - } - - return string(key) -} - -var pool = newPool() - -// Creates a pool with connections to Redis -func newPool() *redis.Pool { - return &redis.Pool{ - MaxIdle: 3, - IdleTimeout: 240 * time.Second, - Dial: func() (redis.Conn, error) { - //c, err := redis.Dial("tcp", ":6379") - c, err := redis.Dial("tcp", revel.Config.StringDefault("redis.server", "127.0.0.1")+":"+revel.Config.StringDefault("redis.port", "6379")) - if err != nil { - return nil, err - } - return c, err - }, - TestOnBorrow: func(c redis.Conn, t time.Time) error { - _, err := c.Do("PING") - return err - }, - } -} - -// Hashs password with bcrypt and returns the string -func HashPassword(password string) (string, error) { - if password == "" { - return "", nil - } - p := []byte(password) - hash, err := bcrypt.GenerateFromPassword(p, 10) - if err != nil { - return "", err - } - return string(hash), nil -} - -// Verify password and hash -func VerifyPassword(password, hash string) (bool, error) { - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - if err != nil { - return false, err - } - return true, nil -} diff --git a/app/db.go b/app/db.go new file mode 100644 index 0000000..16c6c98 --- /dev/null +++ b/app/db.go @@ -0,0 +1,39 @@ +package controllers + +import ( + "github.com/jinzhu/gorm" + _ "github.com/mattn/go-sqlite3" + "log" + "time" +) + +var Db, dberr = gorm.Open(os.Getenv("WEBMON_DB_DRIVER"), os.Getenv("WEBMON_DB_CREDENTIALS")) + +func InitDB() { + if dberr != nil { + log.Panic(dberr) + } + logMode := os.Getenv("WEBMON_DB_LOG") + if logMode == "true" { + Db.LogMode(true) + } + if err := Db.DB().Ping(); err != nil { + log.Panic(err) + } + + db.Debug().CreateTable(&User{}) + db.Debug().CreateTable(&Alert{}) + db.Debug().CreateTable(&Job{}) + db.Debug().CreateTable(&Version{}) + + db.Debug().AutoMigrate(&User{}, &Job{}, &Version{}, &Alert{}) + + db.Debug().Model(&User{}).AddUniqueIndex("idx_user_email", "email") + db.Debug().Model(&User{}).AddUniqueIndex("idx_user_name", "name") + + // Unique index on alert to ensure every user can specify not multiple equally alerts + db.Debug().Model(&Alert{}).AddUniqueIndex("idx_alert_email_userid", "email", "user_id") + + // Unique index to ensure every user can have only one job with the same name + db.Debug().Model(&Job{}).AddUniqueIndex("idx_job_name_userid", "name", "user_id") +} diff --git a/app/handler.go b/app/handler.go new file mode 100644 index 0000000..c3093d6 --- /dev/null +++ b/app/handler.go @@ -0,0 +1,247 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" +) + +func SetupHandler(w http.ResponseWriter, r *http.Request) { + log.Println("Get Setup") + u := User{} + if Db.Find(&u).RecordNotFound() { + PrintRegisterHandler(w, r) + } else { + http.Redirect(w, r, "/login", 302) + } +} + +func RegisterHandler(w http.ResponseWriter, r *http.Request) { + log.Println("Processing registration!") + session, err := store.Get(r, "_SID") + if err != nil { + log.Println(err) + } + u := User{} + err = r.ParseForm() + if err != nil { + log.Println(err) + } + err = decoder.Decode(&u, r.PostForm) + if err != nil { + log.Println(err) + } + u.Password, _ = HashPassword(u.Password) + query := Db.Create(&u) + if query.Error != nil { + session.AddFlash("Registration failed.", "error") + session.Save(r, w) + http.Redirect(w, r, "/register", 302) + return + } + session.AddFlash("Registration completed!", "success") + session.Values["login"] = true + session.Save(r, w) + http.Redirect(w, r, "/admin", 302) +} + +func PrintRegisterHandler(w http.ResponseWriter, r *http.Request) { + log.Println("Get Registration") + session, err := store.Get(r, "_SID") + if err != nil { + log.Println(err) + } + if session.Values["login"] == true { + http.Redirect(w, r, "/admin", 302) + log.Println("Schon erfolgreich eingeloggt!") + return + } + m := Messages{} + m.Success = session.Flashes("success") + m.Error = session.Flashes("error") + session.Save(r, w) + templ := mainTempl.Lookup("login.html") + + err = templ.ExecuteTemplate(w, "register.html", m) + if err != nil { + log.Panic(err) + } + +} + +func AddNewJobHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("Add new job") + session, err := store.Get(r, "_SID") + if err != nil { + log.Println(err) + } + if session.Values["login"] != true { + session.Values["history"] = "/new" + session.Save(r, w) + http.Redirect(w, r, "/login", 302) + return + } + + err = r.ParseForm() + if err != nil { + log.Panic(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + host := Host{} + err = decoder.Decode(&host, r.PostForm) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + u, err := url.Parse(host.Url) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if u.Scheme != "http" && u.Scheme != "https" { + session.AddFlash("Unsurportted scheme. Only http:// and https:// is valid.", "error") + session.Save(r, w) + http.Redirect(w, r, "/admin", 302) + return + } + + host.Monitored = true + host.Host = u.Host + + query := Db.Save(&host) + if query.Error != nil { + log.Println(err) + //session.AddFlash("Befehl konnte auf Grund eines Problems nicht ausgeführt werden.", "error") + session.AddFlash("There was an error. :(", "error") + session.Save(r, w) + http.Redirect(w, r, "/admin", 302) + } + + go func() { + h := []Host{} + Db.Find(&h) + h = CheckPages(h) + for k, _ := range h { + Db.Debug().Save(&h[k]) + } + CacheHosts(cache_prefix+"database", h) + }() + + session.AddFlash("Job added!", "success") + session.Save(r, w) + + http.Redirect(w, r, "/admin", 302) + +} + +func LoginHandler(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, "_SID") + if err != nil { + log.Println(err) + } + u := User{} + err = r.ParseForm() + if err != nil { + log.Println(err) + } + err = decoder.Decode(&u, r.PostForm) + if err != nil { + log.Println(err) + } + log.Println("Processing Login", u.Name) + login := login(u.Name, u.Password, session) + if !login { + session.AddFlash("Login failed.", "error") + session.Save(r, w) + log.Println("Login failed.") + http.Redirect(w, r, "/login", 302) + return + } + session.Values["login"] = true + session.Save(r, w) + var redirectTarget string + if session.Values["history"] != "" && session.Values["history"] != nil { + redirectTarget = fmt.Sprintf("%v", session.Values["history"]) + session.Values["history"] = "" + session.Save(r, w) + http.Redirect(w, r, redirectTarget, 301) + } + http.Redirect(w, r, "/admin", 301) +} + +func PrintLoginHandler(w http.ResponseWriter, r *http.Request) { + log.Println("Get Login") + session, err := store.Get(r, "_SID") + if err != nil { + log.Println(err) + } + + m := Messages{} + m.Success = session.Flashes("success") + m.Error = session.Flashes("error") + session.Save(r, w) + templ := mainTempl.Lookup("login.html") + + err = templ.ExecuteTemplate(w, "login.html", m) + if err != nil { + log.Panic(err) + } +} + +func LogoutHandler(w http.ResponseWriter, r *http.Request) { + log.Println("Logout") + session, _ := store.Get(r, "_SID") + session.Values["login"] = false + session.Save(r, w) + http.Redirect(w, r, "/login", 301) +} + +func DeleteHandler(w http.ResponseWriter, r *http.Request) { + log.Println("Get Delete") + session, err := store.Get(r, "_SID") + if err != nil { + log.Println(err) + } + if session.Values["login"] != true { + session.Values["history"] = "/admin" + session.Save(r, w) + http.Redirect(w, r, "/login", 302) + return + } + err = r.ParseForm() + if err != nil { + log.Println(err) + session.AddFlash("Error parsing form.", "error") + session.Save(r, w) + http.Redirect(w, r, "/admin", 302) + return + } + host := Host{} + err = decoder.Decode(&host, r.URL.Query()) + if err != nil { + log.Println(err) + session.AddFlash("Error parsing form.", "error") + session.Save(r, w) + http.Redirect(w, r, "/admin", 302) + return + } + log.Println("Deleting host where id = ", host.Id) + query := Db.Where("id = ?", host.Id).Delete(&host) + if query.Error != nil { + session.AddFlash("Deleting failed.", "error") + log.Println("Deleting failed.", query.Error) + } else { + session.AddFlash("We removed the host.", "success") + } + session.Save(r, w) + http.Redirect(w, r, "/admin", 302) + go FillCache() +} diff --git a/app/init.go b/app/init.go deleted file mode 100644 index b13043a..0000000 --- a/app/init.go +++ /dev/null @@ -1,43 +0,0 @@ -package app - -import ( - "github.com/anonx/cachesession" - "github.com/cbonello/revel-csrf" - "github.com/revel/revel" -) - -func init() { - // Filters is the default set of global filters. - revel.Filters = []revel.Filter{ - revel.PanicFilter, // Recover from panics and display an error page instead. - revel.RouterFilter, // Use the routing table to select the right Action - revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters. - revel.ParamsFilter, // Parse parameters into Controller.Params. - cachesession.Filter, // Restore and write the session cookie. - revel.FlashFilter, // Restore and write the flash cookie. - csrf.CSRFFilter, // CSRF protection - revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie. - revel.I18nFilter, // Resolve the requested language - HeaderFilter, // Add some security based headers - revel.InterceptorFilter, // Run interceptors around the action. - revel.CompressFilter, // Compress the result. - revel.ActionInvoker, // Invoke the action. - } - - // register startup functions with OnAppStart - // ( order dependent ) - // revel.OnAppStart(InitDB) - // revel.OnAppStart(FillCache) -} - -// TODO turn this into revel.HeaderFilter -// should probably also have a filter for CSRF -// not sure if it can go in the same filter or not -var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) { - // Add some common security headers - c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN") - c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block") - c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff") - - fc[0](c, fc[1:]) // Execute the next filter stage. -} diff --git a/app/main.go b/app/main.go new file mode 100644 index 0000000..00b7ba3 --- /dev/null +++ b/app/main.go @@ -0,0 +1,8 @@ +package main + +import ( + "fmt" +) + +func main() { +} diff --git a/app/struct.go b/app/struct.go new file mode 100644 index 0000000..80d6bf2 --- /dev/null +++ b/app/struct.go @@ -0,0 +1,92 @@ +package main + +import ( + "time" +) + +type User struct { + Id int64 + Email string `sql:"unique"` + Name string `sql:"unique"` + Password string + Confirmed bool + ConfirmationKey string + Alerts []Alert + CreatedAt time.Time + DeletedAt time.Time + UpdatedAt time.Time +} + +// Multiple accounts which are alerted +type Alert struct { + Id int64 + UserId int64 + Email string + CreatedAt time.Time + DeletedAt time.Time + UpdatedAt time.Time +} + +type Job struct { + Id int64 + UserId int64 + Name string + Url string + Versions []Version + Diff string + DiffLen int64 + CreatedAt time.Time + DeletedAt time.Time + UpdatedAt time.Time +} + +// Save history version of jobs +type Version struct { + Id int64 + JobId int64 + Content string + Hash string + CreatedAt time.Time + DeletedAt time.Time + UpdatedAt time.Time +} + +/* Maybe worth saving uptime history? */ + +/* +type Host struct { + Id int64 + Host string + Url string + // Protocoll string // e.g. http + Monitored bool // disable monitoring on error + Private bool + Status string + StatusCode int64 + Success bool + Reason string // Connection failure + Description string + /* + Date time.Time + Include string // Website must include this string + Except string // Website must not include this string + Alert bool // True to send alert on failure + DeletedAt time.Time +*/ +/* + CreatedAt time.Time + UpdatedAt time.Time + Class string +} +*/ + +/* +type Messages struct { + Success []interface{} + Error []interface{} + Hosts []Host + moreScripts []string + NextRun time.Time + Sticky string +} +*/ diff --git a/app/utilities.go b/app/utilities.go new file mode 100644 index 0000000..65a6716 --- /dev/null +++ b/app/utilities.go @@ -0,0 +1,115 @@ +package main + +import ( + "bytes" + "crypto/md5" + "encoding/gob" + "errors" + "fmt" + "github.com/garyburd/redigo/redis" + "golang.org/x/crypto/bcrypt" + // "html/template" + "io" + "io/ioutil" + "log" + "math/rand" + "net/http" + "os" + "time" +) + +// Returns the response from a GET request plus the headers as map and the content as string +func HttpGet(url string) (*http.Response, string, error) { + r, err := http.Get(url) + if err != nil { + return r, "Get request failed.", err + } + + defer r.Body.Close() + contents, err := ioutil.ReadAll(r.Body) + if err != nil { + return r, "Reading body failed.", err + } + + return r, string(contents), nil +} + +// Hashs and returns a string (md5) +func Md5Hash(content string) string { + h := md5.New() + io.WriteString(h, content) + hash := fmt.Sprintf("%x", h.Sum(nil)) + + return hash +} + +// Creates a random string +func RandomKey() string { + rand.Seed(time.Now().UTC().UnixNano()) + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + key := make([]rune, 40) + for i := range key { + key[i] = letters[rand.Intn(len(letters))] + } + + return string(key) +} + +var pool = newPool() + +// Creates a pool with connections to Redis +func newPool() *redis.Pool { + return &redis.Pool{ + MaxIdle: 3, + IdleTimeout: 240 * time.Second, + Dial: func() (redis.Conn, error) { + redis_server := os.Getenv("STATUS_REDIS_SERVER") + redis_port := os.Getenv("STATUS_REDIS_PORT") + c, err := redis.Dial("tcp", redis_server+":"+redis_port) + if err != nil { + return nil, err + } + return c, err + }, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } +} + +// Hashs password with bcrypt and returns the string +func HashPassword(password string) (string, error) { + if password == "" { + return "", errors.New("Empty password.") + } + p := []byte(password) + hash, err := bcrypt.GenerateFromPassword(p, 10) + if err != nil { + return "", err + } + return string(hash), nil +} + +// Verify password and hash +func VerifyPassword(password, hash string) bool { + if hash == "" || password == "" { + return false + } + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + if err != nil { + log.Printf("%s \n", err) + return false + } + return true +} + +func GetBytes(key interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(key) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/app/utilities_test.go b/app/utilities_test.go new file mode 100644 index 0000000..1865c61 --- /dev/null +++ b/app/utilities_test.go @@ -0,0 +1,106 @@ +// +build all general + +package main + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestHttpGet(t *testing.T) { + answer := "Fake webpage here." + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + fmt.Fprintln(w, answer) + })) + defer ts.Close() + + fakeUrl := ts.URL + + resp, content, err := HttpGet(fakeUrl) + if err != nil { + t.Fatal("Error getting web page.") + } + if resp.StatusCode != 200 { + t.Fatal("Expecting 200 as status code.") + } + if strings.TrimSpace(content) != answer { + t.Log("We got this: ", content) + t.Log("We expected this: ", answer) + t.Fatal("Webpage returned wrong answer.") + } +} + +func TestMd5Hash(t *testing.T) { + hash := "f9d08276bc85d30d578e8883f3c7e843" + testHash := Md5Hash("md5hash") + + if hash != testHash { + t.Fatal("Expected %s as hash. Got %s.", hash, testHash) + } +} + +func TestRandomKey(t *testing.T) { + m := map[string]bool{} + var key string + for i := 0; i < 100; i++ { + key = RandomKey() + t.Log(key) // Uncomment to see every generated key. + if m[key] { + t.Fatal("Key not random.") + } else { + m[key] = true + } + if len(key) != 40 { + t.Fatal("Expected a key with length of 40. Got %s.", key) + } + } +} + +func TestPassword(t *testing.T) { + + testHash, err := HashPassword("password") + if err != nil { + t.Fatal("Hashing password failed.") + } + verify := VerifyPassword("password", testHash) + if !verify { + t.Fatal("Verifying password failed.") + } + + testHash, err = HashPassword("") + if err == nil { + t.Fatal("Accepting empty password.") + } + verify = VerifyPassword("", testHash) + if verify { + t.Fatal("Verifying empty password.") + } +} + +func BenchmarkMd5Hash(b *testing.B) { + for i := 0; i < b.N; i++ { + Md5Hash("md5hash") + } +} + +func BenchmarkRandomKey(b *testing.B) { + for i := 0; i < b.N; i++ { + RandomKey() + } +} + +func BenchmarkHashPassword(b *testing.B) { + for i := 0; i < b.N; i++ { + HashPassword("password") + } +} + +func BenchmarkVerifyPassword(b *testing.B) { + for i := 0; i < b.N; i++ { + VerifyPassword("password", "$2a$10$OnsbG0Obaz2af3UkoQ9Jaeky3zfRi.0ZHCJC8DlWnbqbpaXEhWqYe") + } +} diff --git a/app/views/App/Index.html b/app/views/App/Index.html deleted file mode 100644 index deb2304..0000000 --- a/app/views/App/Index.html +++ /dev/null @@ -1,23 +0,0 @@ -{{set . "title" "Home"}} -{{template "header.html" .}} - -
-
-
-
-

It works!

-

-
-
-
-
- -
-
-
- {{template "flash.html" .}} -
-
-
- -{{template "footer.html" .}} diff --git a/app/views/Mailer/ConfirmRegistration.html b/app/views/Mailer/ConfirmRegistration.html deleted file mode 100644 index a63fcba..0000000 --- a/app/views/Mailer/ConfirmRegistration.html +++ /dev/null @@ -1,5 +0,0 @@ -

{{.subject}}

- -

Hello {{.user}},
- to confirm your registration please follow this link: Link -

diff --git a/app/views/Mailer/SendConfirmationKey.html b/app/views/Mailer/SendConfirmationKey.html deleted file mode 100644 index 0fef96b..0000000 --- a/app/views/Mailer/SendConfirmationKey.html +++ /dev/null @@ -1,4 +0,0 @@ -

{{.subject}}

- -

Hello,
- please follow this link to confirm your action: Link

diff --git a/app/views/debug.html b/app/views/debug.html deleted file mode 100644 index f3975b7..0000000 --- a/app/views/debug.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - diff --git a/app/views/errors/404.html b/app/views/errors/404.html deleted file mode 100644 index ebdfe10..0000000 --- a/app/views/errors/404.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - Not found - - -{{if eq .RunMode "dev"}} -{{template "errors/404-dev.html" .}} -{{else}} - {{with .Error}} -

- {{.Title}} -

-

- {{.Description}} -

- {{end}} -{{end}} - - diff --git a/app/views/errors/500.html b/app/views/errors/500.html deleted file mode 100644 index 0cef4de..0000000 --- a/app/views/errors/500.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - Application error - - - {{if eq .RunMode "dev"}} - {{template "errors/500-dev.html" .}} - {{else}} -

Oops, an error occured

-

- This exception has been logged. -

- {{end}} - - diff --git a/app/views/flash.html b/app/views/flash.html deleted file mode 100644 index 9c9ade9..0000000 --- a/app/views/flash.html +++ /dev/null @@ -1,18 +0,0 @@ -{{if .flash.success}} -
- {{.flash.success}} -
-{{end}} - -{{if or .errors .flash.error}} -
- {{if .flash.error}} - {{.flash.error}} - {{end}} - -
-{{end}} diff --git a/app/views/footer.html b/app/views/footer.html deleted file mode 100644 index 8db95e5..0000000 --- a/app/views/footer.html +++ /dev/null @@ -1,5 +0,0 @@ - {{if eq .RunMode "dev"}} - {{template "debug.html" .}} - {{end}} - - diff --git a/app/views/header.html b/app/views/header.html deleted file mode 100644 index 01637f4..0000000 --- a/app/views/header.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{.title}} - - - - - {{range .moreStyles}} - - {{end}} - {{range .moreScripts}} - - {{end}} - - -- cgit v1.2.3