1
0
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

231 lines
6.3 KiB

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"`
Pass string `valid:"runelength(8|999),required"`
Salt string `valid:"optional"`
Email string `valid:"email,required"`
Lists []List
}
type List struct {
gorm.Model
UserID uint
Name string `valid:"alphanum,required"`
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
ret := db.NewRecord(input)
if ret != true {
return ehttp.NewErrorf(http.StatusInternalServerError, "could not create user", err)
}
db.Create(&input)
log.Println("registered user", input.Name)
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)
// 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", ehttp.HandlerFunc(nil)).Methods("POST") // Make a new list
r.Handle("/lists", ehttp.HandlerFunc(nil)).Methods("GET") // Get list of lists
r.Handle("/lists/{name}", ehttp.HandlerFunc(nil)).Methods("PUT") // Update list (name, icon, …)
r.Handle("/lists/{name}", ehttp.HandlerFunc(nil)).Methods("DELETE") // Delete a list
r.Handle("/lists/{name}", ehttp.HandlerFunc(nil)).Methods("GET") // Get list content (i.e., items)
// List sharing
r.Handle("/lists/{name}/sharers", ehttp.HandlerFunc(nil)).Methods("POST") // Add new sharer
r.Handle("/lists/{name}/sharers", ehttp.HandlerFunc(nil)).Methods("GET") // Get list of sharers
r.Handle("/lists/{name}/sharers/{user}", ehttp.HandlerFunc(nil)).Methods("DELETE") // Remove sharer
r.Handle("/lists/{name}/items", ehttp.HandlerFunc(nil)).Methods("PUT") // Add new item
r.Handle("/lists/{name}/items/{item}", ehttp.HandlerFunc(nil)).Methods("DELETE") // Delete item
r.Handle("/lists/{name}/items/{item}", ehttp.HandlerFunc(nil)).Methods("POST") // Update item
// Unit-history (unit is a ever-growing list only used for auto-completion)
r.Handle("/units", ehttp.HandlerFunc(nil)).Methods("POST") // Make a new unit
r.Handle("/units", ehttp.HandlerFunc(nil)).Methods("GET") // Get list of units
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
http.Handle("/", loggedRouter)
http.ListenAndServe(":8000", nil)
}