From e0266d9788be34be0e7da7456168d508ea74d0af Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 15 May 2021 22:56:28 +0200 Subject: [PATCH] finish with create user and start with user login --- config.toml | 1 - example.http | 34 ++++--- go.mod | 1 + go.sum | 2 + modules/config/config.go | 5 +- modules/database/database.go | 3 +- routers/api/v1/user/user.go | 186 +++++++++++++++++++++++++++++------ routers/router/router.go | 29 +----- 8 files changed, 185 insertions(+), 76 deletions(-) diff --git a/config.toml b/config.toml index d39c283..770dc56 100644 --- a/config.toml +++ b/config.toml @@ -1,7 +1,6 @@ [server] debug = true host = "127.0.0.1:3000" -secret = "KAWXQHtKaz8BmWksXsQFPrdE78ptBuwBsaUNt8XcKGZt44QbUp" [database] host = "127.0.0.1:3306" diff --git a/example.http b/example.http index 7c96c52..e27ea04 100644 --- a/example.http +++ b/example.http @@ -1,17 +1,27 @@ -### +#### user login -GET http://localhost:3000/api/v1/users -Content-Type: application/xml -# Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjEyNTAxODcsInVzZXJfaGFzaHRhZyI6IjRIUVQ3NSIsInVzZXJfaWQiOiJhZDAyYzY2NzRmYjI0OWY0YWI4NWU1MjYzZDUyMzAwZiIsInVzZXJuYW1lIjoicnVpc3BpcGUxIn0.wJJO_2maGG_1h2hingzZm3VmNJnmQnpzknU4dnw-8IE -Cookie: session_id=b81aedb75b084d01945ee45134ddbc39; - -### - -POST http://localhost:3000/api/v1/users +POST http://localhost:3000/api/v1/user/login Content-Type: application/json { - "username": "183", - "email": "183@roese.dev", - "password": "teksmkamsdkasd- a , ' . + * maskdmaskdm" + "username": "107", + "password": "my-passworda" +} + +### get users + +POST http://localhost:3000/api/v1/users +Content-Type: application/xml +Cookie: session_id=5CLPfNbit0SCNoyRy2AWslJSWTascm3q + +#### create user + +POST http://localhost:3000/api/v1/users +Content-Type: application/json +User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36 + +{ + "username": "107", + "email": "107@roese.dev", + "password": "my-password" } \ No newline at end of file diff --git a/go.mod b/go.mod index 8669bed..2915578 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/google/uuid v1.2.0 github.com/klauspost/compress v1.12.2 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mileusna/useragent v1.0.2 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect diff --git a/go.sum b/go.sum index 19d06e7..505aa1a 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mileusna/useragent v1.0.2 h1:DgVKtiPnjxlb73z9bCwgdUvU2nQNQ97uhgfO8l9uz/w= +github.com/mileusna/useragent v1.0.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/modules/config/config.go b/modules/config/config.go index efe8f24..3b5f13f 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -13,9 +13,8 @@ type Config struct { } type server struct { - Debug bool - Host string - Secret string + Debug bool + Host string } type database struct { diff --git a/modules/database/database.go b/modules/database/database.go index ac8c18e..ed6f402 100644 --- a/modules/database/database.go +++ b/modules/database/database.go @@ -2,7 +2,6 @@ package database import ( "database/sql" - "errors" "fmt" "git.umbach.dev/app-idea/rest-api/modules/config" @@ -20,7 +19,7 @@ func GetDatabase() (*sql.DB, error) { if err := db.Ping(); err != nil { log.Warnln("DB Connection error:", err.Error()) - return nil, errors.New(err.Error()) + return nil, err } return db, nil diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 9192b02..d40a379 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -1,9 +1,11 @@ package user import ( + "crypto/rand" "database/sql" - "encoding/base64" + "errors" "fmt" + "math/big" "regexp" "strings" "time" @@ -12,14 +14,11 @@ import ( "git.umbach.dev/app-idea/rest-api/modules/database" "github.com/gofiber/fiber/v2" "github.com/google/uuid" + ua "github.com/mileusna/useragent" log "github.com/sirupsen/logrus" - "github.com/zhengxiaowai/shortuuid" "golang.org/x/crypto/bcrypt" ) -//err = bcrypt.CompareHashAndPassword(hashedPassword, []byte("hello wolrd")) -//fmt.Println(err) - type LoginInput struct { Username string `json:"username"` Email string `json:"email"` @@ -92,7 +91,12 @@ func NewUser(c *fiber.Ctx) error { } if input.Hashtag == "" { - input.Hashtag = RandomHashtag(db, 6) + input.Hashtag, err = generateRandomHashtag(db, 6) + + if err != nil { + log.Warn(err) + return c.SendStatus(fiber.StatusInternalServerError) + } } else if !isHashtagValid(db, input.Hashtag) { return c.SendStatus(fiber.StatusUnprocessableEntity) } @@ -101,49 +105,67 @@ func NewUser(c *fiber.Ctx) error { hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) if err != nil { - log.Warnln("bcrypt password error", err) + log.Warnln("Failed to bcrypt password", err) return c.SendStatus(fiber.StatusInternalServerError) } - user_id := strings.Replace(uuid.New().String(), "-", "", -1) + userId := strings.Replace(uuid.New().String(), "-", "", -1) created := time.Now().Format("2006-01-02 15:04:05") // YYYY-MM-DD hh:mm:ss stmt, err := db.Prepare("INSERT INTO users (user_id, user_hashtag, username, email, password, created) VALUES (?, ?, ?, ?, ?, ?);") - stmt.Exec(user_id, input.Hashtag, input.Username, input.Email, hashedPassword, created) + stmt.Exec(userId, input.Hashtag, input.Username, input.Email, hashedPassword, created) stmt.Close() - expires := time.Now().Add(time.Hour * 72) - session_id := strings.Replace(uuid.New().String(), "-", "", -1) + if err != nil { + log.Warnln("Failed to insert user to db", err.Error()) + return c.SendStatus(fiber.StatusInternalServerError) + } - //h := sha256.New() - //h.Write([]byte(config.GetConfig().Server.Secret)) - //b := h.Sum([]byte(session_id)) + sessionId, err := createUserSession(db, userId, c.IP(), string(c.Context().UserAgent())) - //log.Infoln("sha256", h, base64.StdEncoding.EncodeToString(b)) + if err != nil { + log.Warnln(err) + return c.SendStatus(fiber.StatusInternalServerError) + } - stmt2, err := db.Prepare("INSERT INTO sessions (user_id, session_id, expires) VALUES (?, ?, ?);") - stmt2.Exec(user_id, session_id, expires) + expires := getExpiresTime() - stmt2.Close() - - log.Debugln("user created", user_id, input.Hashtag, input.Username, input.Email) - - c.Cookie(&fiber.Cookie{Name: "session_id", Value: base64.StdEncoding.EncodeToString(b), Secure: true, HTTPOnly: true, Expires: expires}) + c.Cookie(&fiber.Cookie{Name: "session_id", Value: sessionId, Secure: true, HTTPOnly: true, Expires: expires}) c.Cookie(&fiber.Cookie{Name: "username", Value: input.Username, Secure: true, Expires: expires}) c.Cookie(&fiber.Cookie{Name: "user_hashtag", Value: input.Hashtag, Secure: true, Expires: expires}) + log.Debugln("user created", userId, input.Hashtag, input.Username, input.Email) + return c.SendStatus(fiber.StatusCreated) } -func RandomHashtag(db *sql.DB, n int) string { +func generateRandomString(n int) (string, error) { + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + r := make([]byte, n) + + for i := 0; i < n; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + return "", err + } + r[i] = letters[num.Int64()] + } + + return string(r), nil +} + +func generateRandomHashtag(db *sql.DB, n int) (string, error) { c := make(chan bool) var s string + var err error for { - su := shortuuid.NewShortUUID() - su.SetAlphabet("ABCDEFGHJKLMNPQRSTUVWXYZ0123456789") - s = su.Random(n) + s, err = generateRandomString(6) + + if err != nil { + return "", errors.New("Error generating Hashtag " + err.Error()) + } go func() { err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", s).Scan(&s) @@ -160,7 +182,7 @@ func RandomHashtag(db *sql.DB, n int) string { } } - return s + return s, nil } func isHashtagValid(db *sql.DB, h string) bool { @@ -170,8 +192,6 @@ func isHashtagValid(db *sql.DB, h string) bool { err := db.QueryRow("SELECT user_hashtag FROM users WHERE user_hashtag = ?", h).Scan(&h) - fmt.Println("isHashtagValid", err == sql.ErrNoRows) - if err == sql.ErrNoRows { return true } @@ -213,9 +233,84 @@ func isEmailAvailable(db *sql.DB, e string) bool { } func SessionIdCheck(c *fiber.Ctx) error { - session_id := c.Cookies("session_id") + valid, err := isSessionIdValid(c.Cookies("session_id")) - log.Infoln("session_id", session_id) + if err != nil { + log.Warn(err) + return fiber.ErrInternalServerError + } + + if valid { + return c.Next() + } + + return fiber.ErrUnauthorized +} + +func isSessionIdValid(sessionId string) (bool, error) { + db, err := database.GetDatabase() + + if db == nil || err != nil { + return false, errors.New("DB error " + err.Error()) + } + + defer db.Close() + + err = db.QueryRow("SELECT session_id FROM sessions WHERE session_id = ?", sessionId).Scan(&sessionId) + + if err == sql.ErrNoRows { + return false, nil + } + return true, nil +} + +func createUserSession(db *sql.DB, userId string, ip string, userAgent string) (string, error) { + sessionId, err := generateRandomString(32) + + if err != nil { + return "", errors.New("Failed to generate user session " + err.Error()) + } + + log.Info(time.Now().String()) + log.Info(getExpiresTime()) + + stmt, err := db.Prepare("INSERT INTO sessions (user_id, session_id, ip, user_agent, last_login, expires) VALUES (?, ?, ?, ?, ?, ?);") + + if err != nil { + return "", errors.New("Failed to insert user into db " + err.Error()) + } + + ua := ua.Parse(userAgent) + + stmt.Exec(userId, sessionId, ip, ua.OS+" "+ua.Name, time.Now(), getExpiresTime()) + stmt.Close() + + return "", nil +} + +func getExpiresTime() time.Time { + // TODO: db default + return time.Now().Add(time.Hour * 72) +} + +func Login(c *fiber.Ctx) error { + var input LoginInput + + if err := c.BodyParser(&input); err != nil { + return c.SendStatus(fiber.StatusBadRequest) + } + + log.Println(input) + + if input.Username != "" && !isValid(input.Username, 3, 30) { + log.Info("for1") + return c.SendStatus(fiber.StatusBadRequest) + } + + if input.Email != "" && !isEmailValid(input.Email) { + log.Info("for2") + return c.SendStatus(fiber.StatusForbidden) + } db, err := database.GetDatabase() @@ -223,9 +318,36 @@ func SessionIdCheck(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusInternalServerError) } + var hashedPassword string + + if input.Username != "" { + err = db.QueryRow("SELECT password FROM users WHERE username = ?", input.Username).Scan(&hashedPassword) + + log.Info(hashedPassword) + } else { + + } + + if err != nil { + log.Infoln("una", err) + return c.SendStatus(fiber.StatusUnauthorized) + } + + err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(input.Password)) + + if err != nil { + log.Warnln("Failed to comapare bcrypt password", err) + return c.SendStatus(fiber.StatusUnauthorized) + } + + log.Info("password correct") + + //err = bcrypt.CompareHashAndPassword(hashedPassword, []byte("hello wolrd")) + //fmt.Println(err) + defer db.Close() - return fiber.ErrUnauthorized + return c.JSON(fiber.Map{"test": hashedPassword}) } func GetUser(c *fiber.Ctx) error { diff --git a/routers/router/router.go b/routers/router/router.go index 4fe8ee9..be1dad1 100644 --- a/routers/router/router.go +++ b/routers/router/router.go @@ -3,38 +3,15 @@ package router import ( "git.umbach.dev/app-idea/rest-api/routers/api/v1/user" - jwt "github.com/form3tech-oss/jwt-go" "github.com/gofiber/fiber/v2" ) -/* -func Protected() fiber.Handler { - return jwtware.New(jwtware.Config{ - SigningKey: []byte(config.GetConfig().Server.Secret), - ErrorHandler: jwtError, - }) -} - -func jwtError(c *fiber.Ctx, err error) error { - if err.Error() == "Missing or malformed JWT" { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "Missing or malformed JWT"}) - } - return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid or expired JWT"}) -} */ - -func Test(c *fiber.Ctx) error { - usert := c.Locals("user").(*jwt.Token) - claims := usert.Claims.(jwt.MapClaims) - name := claims["username"].(string) - return c.SendString("Welcome" + name) -} - func SetupRoutes(app *fiber.App) { api := app.Group("/api/v1") - /* Unauthenticated routes */ - - app.Get("/user", user.GetUser) + u := api.Group("/user") + u.Get("/user", user.GetUser) + u.Post("/login", user.Login) users := api.Group("/users") users.Post("/", user.NewUser)