added login api

This commit is contained in:
2025-01-25 17:39:13 +01:00
parent 57acac6dae
commit b6b92d38be
6 changed files with 124 additions and 31 deletions

15
config/config.go Normal file
View File

@@ -0,0 +1,15 @@
package config
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_]+$`
const BCRYPT_COST = 16

View File

@@ -73,3 +73,32 @@ func InsertUser(db *sql.DB, username string, passwordHash string) (string, error
}
return userId, err
}
func GetPasswordHash(db *sql.DB, username string) (string, error) {
query := `
SELECT password_hash FROM Accounts
WHERE LOWER(username) = LOWER($1);
`
var passwordHash string
err := db.QueryRow(query, username).Scan(&passwordHash)
if err != nil {
return "", fmt.Errorf("error getting password: %v", err)
}
return passwordHash, err
}
func GetUserId(db *sql.DB, username string) (string, error) {
query := `
SELECT user_id, username AS dbUsername FROM Accounts
WHERE LOWER(username) = $1;
`
var dbUsername string
err := db.QueryRow(query, username).Scan(&dbUsername, &dbUsername)
if err != nil {
return "", fmt.Errorf("error getting user id: %v", err)
}
return dbUsername, err
}

View File

@@ -1,11 +1,12 @@
package handlers
import (
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
"log"
"os"
"relay-server/config"
"relay-server/database"
"relay-server/helpers"
"relay-server/models"
@@ -25,12 +26,10 @@ func Signup(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "password is empty"})
}
// Checks if passwords have valid length and characters
// Checks if passwords or username 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) {
} else if !helpers.IsValidUsername(u.Username) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid username"})
}
@@ -41,9 +40,9 @@ func Signup(c *fiber.Ctx) error {
}
// Create password hash
passwordHash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
passwordHash, err := bcrypt.GenerateFromPassword([]byte(u.Password), config.BCRYPT_COST)
if err != nil {
log.Printf("error hashing password: %v", err)
fmt.Printf("error hashing password: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "internal server error"})
}
@@ -70,5 +69,60 @@ func Signup(c *fiber.Ctx) error {
// 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})
}
func Login(c *fiber.Ctx) error {
db, _ := database.InitDatabase()
u := new(models.LoginStruct)
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 username or passwords have valid length and characters
if !helpers.IsValidUsername(u.Username) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid username"})
} else if !helpers.IsValidPassword(u.Password) {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid password"})
}
// Checks if username exist in database
exist, _ := database.CheckUserExists(db, u.Username)
if !exist {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "user does not exists"})
}
// Verifies password matching
passwordHash, _ := database.GetPasswordHash(db, u.Password)
if bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(u.Password)) != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid password"})
}
userId, err := database.GetUserId(db, u.Username)
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
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Successfully signed up", "username": u.Username, "user_id": userId})
}

View File

@@ -2,20 +2,9 @@ package helpers
import (
"regexp"
"relay-server/config"
)
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)
@@ -23,15 +12,15 @@ func IsValidUsername(username interface{}) bool {
return false
}
match, _ := regexp.MatchString(USERNAME_REGEX, strUsername)
match, _ := regexp.MatchString(config.USERNAME_REGEX, strUsername)
if !match {
return false
}
// Checks if username length is valid
if len(strUsername) < MIN_USERNAME_LENGTH {
if len(strUsername) < config.MIN_USERNAME_LENGTH {
return false
}
if len(strUsername) > MAX_USERNAME_LENGTH {
if len(strUsername) > config.MAX_USERNAME_LENGTH {
return false
}
@@ -44,15 +33,15 @@ func IsValidPassword(password interface{}) bool {
return false
}
if len(strPassword) < MIN_PASSWORD_LENGTH {
if len(strPassword) < config.MIN_PASSWORD_LENGTH {
return false
}
if len(strPassword) > MAX_PASSWORD_LENGTH {
if len(strPassword) > config.MAX_PASSWORD_LENGTH {
return false
}
match, _ := regexp.MatchString(PASSWORD_REGEX, strPassword)
match, _ := regexp.MatchString(config.PASSWORD_REGEX, strPassword)
if !match {
return false
}

11
main.go
View File

@@ -20,17 +20,18 @@ 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.Get("/users", func(c *fiber.Ctx) error {
// users, _ := database.GetUsers(db)
// return c.JSON(fiber.Map{"users": users})
//})
app.Post("/api/auth/signup", handlers.Signup)
app.Post("/api/auth/login", handlers.Login)
err = app.Listen(":3000")
if err != nil {
return

View File

@@ -1,5 +1,10 @@
package models
type LoginStruct struct {
Username string `json:"username" xml:"username" form:"username"`
Password string `json:"password" xml:"password" form:"password"`
}
type SignupStruct struct {
Username string `json:"username" xml:"username" form:"username"`
Password string `json:"password" xml:"password" form:"password"`