code refactor
This commit is contained in:
@@ -17,12 +17,12 @@ func InitDatabase() (*sql.DB, error) {
|
||||
password := os.Getenv("PG_PASSWORD")
|
||||
host := os.Getenv("PG_HOST")
|
||||
connStr := fmt.Sprintf("user=postgres host=%s dbname=relay password=%s sslmode=disable", host, password)
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
DB, err := sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
return DB, nil
|
||||
|
||||
}
|
||||
|
||||
@@ -44,3 +44,32 @@ func GetUsers(db *sql.DB) ([]string, error) {
|
||||
}
|
||||
return users, err
|
||||
}
|
||||
|
||||
func CheckUserExists(db *sql.DB, username string) (bool, error) {
|
||||
query := `SELECT COUNT(1) FROM accounts WHERE username= $1`
|
||||
|
||||
var count int
|
||||
|
||||
err := db.QueryRow(query, username).Scan(&count)
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error checking username exists: %v", err)
|
||||
}
|
||||
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func InsertUser(db *sql.DB, username string, passwordHash string) (string, error) {
|
||||
query := `
|
||||
INSERT INTO Accounts (username, password_hash)
|
||||
VALUES ($1, $2)
|
||||
RETURNING user_id;
|
||||
`
|
||||
|
||||
var userId string
|
||||
err := db.QueryRow(query, username, passwordHash).Scan(&userId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error inserting user: %v", err)
|
||||
}
|
||||
return userId, err
|
||||
}
|
||||
|
||||
4
go.mod
4
go.mod
@@ -6,10 +6,12 @@ require (
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
golang.org/x/crypto v0.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
@@ -19,5 +21,5 @@ require (
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
)
|
||||
|
||||
8
go.sum
8
go.sum
@@ -2,6 +2,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@@ -25,7 +27,13 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
||||
74
handlers/signup.go
Normal file
74
handlers/signup.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"log"
|
||||
"os"
|
||||
"relay-server/database"
|
||||
"relay-server/helpers"
|
||||
"relay-server/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Signup(c *fiber.Ctx) error {
|
||||
db, _ := database.InitDatabase()
|
||||
u := new(models.SignupStruct)
|
||||
if err := c.BodyParser(u); err != nil {
|
||||
return err
|
||||
}
|
||||
// Checks if username or passwords are empty
|
||||
if u.Username == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "username is empty"})
|
||||
} else if u.Password == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "password is empty"})
|
||||
}
|
||||
|
||||
// Checks if passwords have valid length and characters
|
||||
if !helpers.IsValidPassword(u.Password) {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid password"})
|
||||
}
|
||||
// Checks if username have valid length and characters
|
||||
if !helpers.IsValidUsername(u.Username) {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid username"})
|
||||
}
|
||||
|
||||
// Checks if username already exist in database
|
||||
exist, _ := database.CheckUserExists(db, u.Username)
|
||||
if exist {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "user already exists"})
|
||||
}
|
||||
|
||||
// Create password hash
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
log.Printf("error hashing password: %v", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "internal server error"})
|
||||
}
|
||||
|
||||
// Insert username and password hash to database
|
||||
userId, err := database.InsertUser(db, u.Username, string(passwordHash))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal server error"})
|
||||
}
|
||||
|
||||
// Generate token with user id and username
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"user_id": userId,
|
||||
"username": u.Username,
|
||||
})
|
||||
// Sign token
|
||||
signedToken, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
|
||||
|
||||
// Set token to cookies
|
||||
cookie := new(fiber.Cookie)
|
||||
cookie.Name = "token"
|
||||
cookie.Value = signedToken
|
||||
cookie.Expires = time.Now().Add(30 * 24 * time.Hour)
|
||||
cookie.HTTPOnly = true
|
||||
|
||||
// If everything went well sent username and user_id assigned by database
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Successfully signed up", "username": u.Username, "user_id": userId})
|
||||
|
||||
}
|
||||
61
helpers/filter.go
Normal file
61
helpers/filter.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const MIN_USERNAME_LENGTH = 4
|
||||
|
||||
const MAX_USERNAME_LENGTH = 20
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 8
|
||||
|
||||
const MAX_PASSWORD_LENGTH = 128
|
||||
|
||||
const PASSWORD_REGEX = `^[A-Za-z0-9!@#$%^&*(),.?":{}|<>]+$`
|
||||
|
||||
const USERNAME_REGEX = `^[a-zA-Z0-9_]+$`
|
||||
|
||||
func IsValidUsername(username interface{}) bool {
|
||||
// Checks if username is type of string
|
||||
strUsername, ok := username.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
match, _ := regexp.MatchString(USERNAME_REGEX, strUsername)
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
// Checks if username length is valid
|
||||
if len(strUsername) < MIN_USERNAME_LENGTH {
|
||||
return false
|
||||
}
|
||||
if len(strUsername) > MAX_USERNAME_LENGTH {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsValidPassword(password interface{}) bool {
|
||||
strPassword, ok := password.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(strPassword) < MIN_PASSWORD_LENGTH {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(strPassword) > MAX_PASSWORD_LENGTH {
|
||||
return false
|
||||
}
|
||||
|
||||
match, _ := regexp.MatchString(PASSWORD_REGEX, strPassword)
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
11
main.go
11
main.go
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"log"
|
||||
"relay-server/database"
|
||||
"relay-server/handlers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -19,11 +20,19 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(db)
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
|
||||
app.Get("/users", func(c *fiber.Ctx) error {
|
||||
users, _ := database.GetUsers(db)
|
||||
return c.JSON(fiber.Map{"users": users})
|
||||
})
|
||||
|
||||
app.Listen(":3000")
|
||||
app.Post("/api/auth/signup", handlers.Signup)
|
||||
|
||||
err = app.Listen(":3000")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
6
models/signup.go
Normal file
6
models/signup.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
type SignupStruct struct {
|
||||
Username string `json:"username" xml:"username" form:"username"`
|
||||
Password string `json:"password" xml:"password" form:"password"`
|
||||
}
|
||||
Reference in New Issue
Block a user