package main import ( "fmt" "net/http" "encoding/json" "crypto/rand" "reflect" "log" "os" // muxer and form parser "github.com/gorilla/mux" "github.com/gorilla/handlers" "github.com/gorilla/schema" // error returns for HandlerFunc "github.com/creack/ehttp" // JWT stuff "github.com/auth0/go-jwt-middleware" "github.com/dgrijalva/jwt-go" // form validation "github.com/asaskevich/govalidator" // database backend "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" // password hashing "golang.org/x/crypto/scrypt" ) type User struct { gorm.Model Name string `valid:"alphanum,required" sql:"unique"` Pass string `valid:"runelength(8|999),required"` Salt string `valid:"optional"` Email string `valid:"email,required" sql:"unique"` Lists []List } type List struct { gorm.Model UserID uint `gorm:"unique_index:idx_userid_name"` Name string `valid:"alphanum,required" gorm:"unique_index:idx_userid_name"` Icon string `valid:"alphanum,optional"` Items []Item } type Item struct { gorm.Model ListID uint Name string `valid:"alphanum,required"` Amount float64 `valid:"optional"` Bought bool `valid:"required"` Unit Unit UnitID uint } type Unit struct { gorm.Model Name string `valid:"alphanum,required"` } var db *gorm.DB func getToken(w http.ResponseWriter, r *http.Request) error { var userInput User var userRecord User var err error decoder := schema.NewDecoder() // Parse form data err = r.ParseForm() if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not parse form", err) } // Decode to struct err = decoder.Decode(&userInput, r.Form) if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not decode user from form", err) } // Find user in DB db.First(&userRecord) if db.Error != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "database lookup error", err) } // Check hashed PW hash, err := scrypt.Key([]byte(userInput.Pass), []byte(userRecord.Salt), 16384, 8, 1, 32) if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "cannot hash pass", err) } if userRecord.Name == userInput.Name && reflect.DeepEqual(hash, []byte(userRecord.Pass)) { // Generate JWT-token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "name":userRecord.Name, }) tokenString, err := token.SignedString([]byte("TODO")) if err != nil { return ehttp.NewErrorf(http.StatusForbidden, "could not construct token", err) } // Reply with token jsonOut, _ := json.Marshal(map[string]string{"token": tokenString}) fmt.Fprint(w, string(jsonOut)) return nil } // User was not found return ehttp.NewErrorf(http.StatusForbidden, "Could not find user/pass") } func register(w http.ResponseWriter, r *http.Request) error { decoder := schema.NewDecoder() // Parse http request to request-struct err := r.ParseForm() if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not parse form", err) } // Decode request into struct var input User err = decoder.Decode(&input, r.PostForm) if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not decode user", err) } // Validate struct res, err := govalidator.ValidateStruct(input) if err != nil || res != true { return ehttp.NewErrorf(http.StatusBadRequest, "could not validate your data", err) } // Check if users already exists /*countName := 0; countEmail := 0 db.Model(&User{}).Where("name = ?", input.Name).Count(&countName) db.Model(&User{}).Where("email = ?", input.Email).Count(&countEmail) if countName != 0 || countEmail != 0{ return ehttp.NewErrorf(http.StatusConflict, "username or email already exists") }*/ // Generate salt salt := make([]byte, 64) _, err = rand.Read(salt) if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not generate salt", err) } input.Salt = string(salt) // Generate scrypt hash of pass hash, err := scrypt.Key([]byte(input.Pass), salt, 16384, 8, 1, 32) if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not hash pass", err) } input.Pass = string(hash) // add to database db.NewRecord(input) retCreate := db.Create(&input) if retCreate.Error != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not create user", err) } log.Println("registered user", input.Name) return nil } func createList(w http.ResponseWriter, r *http.Request) error { decoder := schema.NewDecoder() decoder.IgnoreUnknownKeys(true) // Parse http request to request-struct err := r.ParseForm() if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not parse form", err) } // Decode request into struct var input List err = decoder.Decode(&input, r.PostForm) if err != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "could not decode ", err) } // Validate struct res, err := govalidator.ValidateStruct(input) if err != nil || res != true { return ehttp.NewErrorf(http.StatusBadRequest, "could not validate your data", err) } // Find request-corresponding user // TODO: push into middleware? userName := r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) user := User{Name: userName} db.Where(&user).First(&user) if user.ID == 0 { // TODO: ugly way to check for user-not-found return ehttp.NewErrorf(http.StatusForbidden, "cannot find claimed user") } // create the new list db.NewRecord(&input) ret := db.Create(&input) if ret.Error != nil { return ehttp.NewErrorf(http.StatusInternalServerError, "cannot create list in db") } return nil } func main() { var err error // TODO: maybe move to init()? db, err = gorm.Open("sqlite3", "test.db") if err != nil { log.Fatalln("cannot open db", err) } db.LogMode(false) jwt := jwtmiddleware.New(jwtmiddleware.Options{ Extractor: func(r *http.Request) (string,error) { r.ParseForm() return r.Form["token"][0], nil }, ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { return []byte("TODO"), nil }}) var jHdlr = jwt.Handler // make an alias so routes will be more readable // Create or update schemas … db.AutoMigrate(&User{}) db.AutoMigrate(&List{}) db.AutoMigrate(&Item{}) db.AutoMigrate(&Unit{}) db.Model(&User{}).Related(&List{}) db.Model(&List{}).Related(&Item{}) db.Model(&Item{}).Related(&Unit{}) defer db.Close() // define URL handlers r := mux.NewRouter() // User Management r.Handle("/users", ehttp.HandlerFunc(register)).Methods("POST") // Make new user r.Handle("/users", ehttp.HandlerFunc(getToken)).Methods("GET") // Get user „info“(=token) // TODO: delete, modify // List Management r.Handle("/lists", jHdlr(ehttp.HandlerFunc(createList))).Methods("POST") // Make a new list r.Handle("/lists", jHdlr(ehttp.HandlerFunc(nil))).Methods("GET") // Get list of lists r.Handle("/lists/{name}", jHdlr(ehttp.HandlerFunc(nil))).Methods("PUT") // Update list (name, icon, …) r.Handle("/lists/{name}", jHdlr(ehttp.HandlerFunc(nil))).Methods("DELETE") // Delete a list r.Handle("/lists/{name}", jHdlr(ehttp.HandlerFunc(nil))).Methods("GET") // Get list content (i.e., items) // List sharing r.Handle("/lists/{name}/sharers", jHdlr(ehttp.HandlerFunc(nil))).Methods("POST") // Add new sharer r.Handle("/lists/{name}/sharers", jHdlr(ehttp.HandlerFunc(nil))).Methods("GET") // Get list of sharers r.Handle("/lists/{name}/sharers/{user}", jHdlr(ehttp.HandlerFunc(nil))).Methods("DELETE") // Remove sharer r.Handle("/lists/{name}/items", jHdlr(ehttp.HandlerFunc(nil))).Methods("PUT") // Add new item r.Handle("/lists/{name}/items/{item}", jHdlr(ehttp.HandlerFunc(nil))).Methods("DELETE") // Delete item r.Handle("/lists/{name}/items/{item}", jHdlr(ehttp.HandlerFunc(nil))).Methods("POST") // Update item // Unit-history (unit is a ever-growing list only used for auto-completion) r.Handle("/units", jHdlr(ehttp.HandlerFunc(nil))).Methods("POST") // Make a new unit r.Handle("/units", jHdlr(ehttp.HandlerFunc(nil))).Methods("GET") // Get list of units loggedRouter := handlers.LoggingHandler(os.Stdout, r) http.Handle("/", loggedRouter) http.ListenAndServe(":8000", nil) }