diff options
| author | Horus3 | 2015-02-10 08:33:42 +0100 |
|---|---|---|
| committer | Horus3 | 2015-02-10 08:33:42 +0100 |
| commit | 8c30749613bcf1ce47fff0a6d1a60c34f91f01c4 (patch) | |
| tree | 98354473d929cbc9749f08ea0afdf90993a01480 | |
| parent | af1f4677c685e8a2c4967ffa0350d314a6543db7 (diff) | |
| download | webmon-8c30749613bcf1ce47fff0a6d1a60c34f91f01c4.tar.gz | |
Basic user control.
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | app/controllers/app.go | 147 | ||||
| -rw-r--r-- | app/controllers/db.go | 84 | ||||
| -rw-r--r-- | app/controllers/url.go | 28 | ||||
| -rw-r--r-- | app/controllers/utilities.go | 100 | ||||
| -rw-r--r-- | app/init.go | 9 | ||||
| -rw-r--r-- | app/mailers/mail.go | 15 | ||||
| -rw-r--r-- | conf/app.conf | 11 |
8 files changed, 350 insertions, 45 deletions
@@ -1,3 +1,4 @@ test-results/ tmp/ routes/ +*.swp diff --git a/app/controllers/app.go b/app/controllers/app.go index e76d76b..4aad9a3 100644 --- a/app/controllers/app.go +++ b/app/controllers/app.go @@ -1,6 +1,9 @@ package controllers -import "github.com/revel/revel" +import ( + "github.com/garyburd/redigo/redis" + "github.com/revel/revel" +) type App struct { *revel.Controller @@ -9,3 +12,145 @@ type App struct { 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(confirm).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.") + c.Validation.Required(VerifyPassword(password, u.password)).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"] = u.id + + return c.Redirect(App.Account) + } else { + db.Where("email = ?", email).First(&u) + // Get random string + key := RandomKey() + // Set key in redis + conn := pool.Get() + defer conn.Close() + _, err := conn.Do("SET", key, u.email, 86400) + // Send email with confirmation link + // TODO Implementing the function + SendConfirmationKey(email, key) + + // TODO Print message that a mail was sent + return c.Redirect(App.PrintLogin) + } +} + +func (c App) Confirm(key string) revel.Result { + + 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 = ?", email).First(&u) + + if u.confirmed == false { + // E-Mail is now confirmed + u.confirmed = true + db.Save(&u) + } + + c.Session["login"] = "true" + c.Session["uid"] = u.id + + return c.Redirect(App.Account) +} + +func (c App) Register(email, confirmEmail, user, password, confirmPassword string) revel.Result { + + c.Validation.Required(email).Messagel("Please provide a mail adress.") + c.Validation.Required(email == confirmEmail).Messagel("The mail adresses do not match.") + c.Validation.Required(user).Messagel("Please provide a user name.") + + if password != "" { + c.Validation.Required(password == confirmPassword).Messagel("Passwords do not match.") + } + + if c.Validation.HasErrors() { + c.Validation.Keep() + c.FlashParams() + return c.Redirect(App.PrintRegister) + } + + p := HashPassword(password) + u := User{ + Name: user, + Email: email, + Password: p, + Confirmed: false, + Alerts: []Alert{{Email: email}}, + } + + db.NewRecord(user) + db.Create(&user) + db.Save(&user) + + // Create key to confirm mail adress + key := RandomKey() + + // Redis + conn := pool.Get() + defer conn.Close() + _, err := conn.Do("SET", key, email) + + // Send email with confirmation link + // TODO Implementing the function + SendConfirmationKey(email, key) + c.Flash.Success("A mail with a confirmation link was sent. Please confirm your mail adress now.") + + return c.Redirect(App.PrintLogin) +} diff --git a/app/controllers/db.go b/app/controllers/db.go index 9607f24..f6a535e 100644 --- a/app/controllers/db.go +++ b/app/controllers/db.go @@ -4,40 +4,96 @@ import ( "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" "github.com/revel/revel" + "time" ) type User struct { - Id int64 - Email string - Name string - Password string + Id int64 + Email string `sql:"unique"` + Name string `sql:"unique"` + Password string + Confirmed bool + 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 } -var DB *gorm.DB +// 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 +} -func DBInit() *sql.DB { +var db = DBInit() + +func DBInit() *gorm.DB { // Open database handler - db, err := gorm.Open("sqlite3", "/tmp/gorm.db") + db, err := gorm.Open(revel.Config.String("db.driver"), revel.Config.String("db.spec")) + + // 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 defined as struct - db.CreateTable(&User{}) - db.CreateTable(&Job{}) + // 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") - // Add index on email - db.Model(&User{}).AddIndex("idx_user_email", "email") + // Makes hash unique + // db.Debug().Model(&Version{}).AddUniqueIndex("idx_version_hash", "hash") - // Make database handler public - DB = db + return db } diff --git a/app/controllers/url.go b/app/controllers/url.go deleted file mode 100644 index 516f6a6..0000000 --- a/app/controllers/url.go +++ /dev/null @@ -1,28 +0,0 @@ -package controllers - -import ( - "crypto/md5" - "fmt" - "io" - "io/ioutil" - "net/http" -) - -func HashUrl(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 er != nil { - return "Reading body failed.", err - } - - h := md5.New() - io.WriteString(h, string(contents)) - hash := fmt.Sprintf("%x", h.Sum(nil)) - - return hash, nil -} diff --git a/app/controllers/utilities.go b/app/controllers/utilities.go new file mode 100644 index 0000000..2eae775 --- /dev/null +++ b/app/controllers/utilities.go @@ -0,0 +1,100 @@ +package controllers + +import ( + "crypto/md5" + "fmt" + "github.com/garyburd/redigo/redis" + "github.com/tanema/revel_mailer" + "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 er != 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") + if revel.Config.Bool("cache.redis") { + // If we use redis as cache we reuse the config part + c, err := redis.Dial("tcp", revel.Config.String("cache.hosts")) + } else { + // Otherwise we use our own configuration + c, err := redis.Dial("tcp", revel.Config.String("redis.server")+":"+revel.Config.String("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 nil, nil + } + p := []byte(password) + hash, err := bcrypt.GenerateFromPassword(p, 10) + if err != nil { + return nil, err + } + return string(hash) +} + +// Verify password and hash +func VerifyPassword(password, hash string) (bool, error) { + err := bcrypt.CompareHashAndPassword(hash, password) + if err != nil { + return false, err + } + return true, nil +} diff --git a/app/init.go b/app/init.go index 2305d73..b13043a 100644 --- a/app/init.go +++ b/app/init.go @@ -1,6 +1,10 @@ package app -import "github.com/revel/revel" +import ( + "github.com/anonx/cachesession" + "github.com/cbonello/revel-csrf" + "github.com/revel/revel" +) func init() { // Filters is the default set of global filters. @@ -9,8 +13,9 @@ func init() { 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. - revel.SessionFilter, // Restore and write the session cookie. + 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 diff --git a/app/mailers/mail.go b/app/mailers/mail.go new file mode 100644 index 0000000..1c3a198 --- /dev/null +++ b/app/mailers/mail.go @@ -0,0 +1,15 @@ +package mailers + +import "github.com/tanema/revel_mailer" + +type Mailer struct { + revel_mailer.Mailer +} + +func (u Mailer) SendConfirmationKey(email, key) { + return u.Send(revel_mailer.H{ + "subject": "Confirmation Key", + "to": []string{email}, + "key": key, + }) +} diff --git a/conf/app.conf b/conf/app.conf index c923753..0c2dbf3 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -15,6 +15,17 @@ app.name = webmon # into your application app.secret = vQmfAomzCfewNr0rJlmvQ9Dv42wVMy6x3l5A0X3MsGgAgl95huCHW4fO1hsjkjnn +db.import = "github.com/mattn/go-sqlite3" +db.driver = "sqlite3" +db.spec = "/tmp/gorb.db" + +redis.server = "127.0.0.1" +redis.port = "6379" + +mail.host = localhost +mail.port = 25 +mail.from = webmon +mail.user = webmon@iamfabulous.de # The IP address on which to listen. http.addr = |
