added login api
This commit is contained in:
15
config/config.go
Normal file
15
config/config.go
Normal 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
|
||||||
@@ -73,3 +73,32 @@ func InsertUser(db *sql.DB, username string, passwordHash string) (string, error
|
|||||||
}
|
}
|
||||||
return userId, err
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"relay-server/config"
|
||||||
"relay-server/database"
|
"relay-server/database"
|
||||||
"relay-server/helpers"
|
"relay-server/helpers"
|
||||||
"relay-server/models"
|
"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"})
|
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) {
|
if !helpers.IsValidPassword(u.Password) {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid password"})
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid password"})
|
||||||
}
|
} else if !helpers.IsValidUsername(u.Username) {
|
||||||
// Checks if username have valid length and characters
|
|
||||||
if !helpers.IsValidUsername(u.Username) {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid 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
|
// 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 {
|
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"})
|
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
|
// 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})
|
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})
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,9 @@ package helpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"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 {
|
func IsValidUsername(username interface{}) bool {
|
||||||
// Checks if username is type of string
|
// Checks if username is type of string
|
||||||
strUsername, ok := username.(string)
|
strUsername, ok := username.(string)
|
||||||
@@ -23,15 +12,15 @@ func IsValidUsername(username interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
match, _ := regexp.MatchString(USERNAME_REGEX, strUsername)
|
match, _ := regexp.MatchString(config.USERNAME_REGEX, strUsername)
|
||||||
if !match {
|
if !match {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Checks if username length is valid
|
// Checks if username length is valid
|
||||||
if len(strUsername) < MIN_USERNAME_LENGTH {
|
if len(strUsername) < config.MIN_USERNAME_LENGTH {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(strUsername) > MAX_USERNAME_LENGTH {
|
if len(strUsername) > config.MAX_USERNAME_LENGTH {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,15 +33,15 @@ func IsValidPassword(password interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(strPassword) < MIN_PASSWORD_LENGTH {
|
if len(strPassword) < config.MIN_PASSWORD_LENGTH {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(strPassword) > MAX_PASSWORD_LENGTH {
|
if len(strPassword) > config.MAX_PASSWORD_LENGTH {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
match, _ := regexp.MatchString(PASSWORD_REGEX, strPassword)
|
match, _ := regexp.MatchString(config.PASSWORD_REGEX, strPassword)
|
||||||
if !match {
|
if !match {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
11
main.go
11
main.go
@@ -20,17 +20,18 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}(db)
|
}(db)
|
||||||
|
|
||||||
app.Get("/", func(c *fiber.Ctx) error {
|
app.Get("/", func(c *fiber.Ctx) error {
|
||||||
return c.SendString("Hello, World!")
|
return c.SendString("Hello, World!")
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/users", func(c *fiber.Ctx) error {
|
//app.Get("/users", func(c *fiber.Ctx) error {
|
||||||
users, _ := database.GetUsers(db)
|
// users, _ := database.GetUsers(db)
|
||||||
return c.JSON(fiber.Map{"users": users})
|
// return c.JSON(fiber.Map{"users": users})
|
||||||
})
|
//})
|
||||||
|
|
||||||
app.Post("/api/auth/signup", handlers.Signup)
|
app.Post("/api/auth/signup", handlers.Signup)
|
||||||
|
app.Post("/api/auth/login", handlers.Login)
|
||||||
err = app.Listen(":3000")
|
err = app.Listen(":3000")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
type LoginStruct struct {
|
||||||
|
Username string `json:"username" xml:"username" form:"username"`
|
||||||
|
Password string `json:"password" xml:"password" form:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
type SignupStruct struct {
|
type SignupStruct struct {
|
||||||
Username string `json:"username" xml:"username" form:"username"`
|
Username string `json:"username" xml:"username" form:"username"`
|
||||||
Password string `json:"password" xml:"password" form:"password"`
|
Password string `json:"password" xml:"password" form:"password"`
|
||||||
Reference in New Issue
Block a user