package handlers import ( "fmt" "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "os" "relay-server/config" "relay-server/database" "relay-server/utils" "time" ) func Signup(c *fiber.Ctx) error { type SignupStruct struct { Username string `json:"username" xml:"username" form:"username"` Password string `json:"password" xml:"password" form:"password"` } u := new(SignupStruct) if err := c.BodyParser(u); err != nil { return utils.NewError(utils.ErrInvalidInput, "Invalid request body", err) } // Validate input if u.Username == "" { return utils.NewError(utils.ErrInvalidInput, "Username is empty", nil) } if u.Password == "" { return utils.NewError(utils.ErrInvalidInput, "Password is empty", nil) } if !utils.IsValidPassword(u.Password) { return utils.NewError(utils.ErrInvalidInput, "Invalid password", nil) } if !utils.IsValidUsername(u.Username) { return utils.NewError(utils.ErrInvalidInput, "Invalid username", nil) } // Check if user exists exist, err := database.CheckUserExists(database.DB, u.Username) if err != nil { return err } if exist { return utils.NewError(utils.ErrInvalidInput, "User already exists", nil) } // Create password hash passwordHash, err := bcrypt.GenerateFromPassword([]byte(u.Password), config.BCRYPT_COST) if err != nil { return utils.NewError(utils.ErrInternal, "internal server error", fmt.Errorf("failed to generate password hash: %w", err)) } // Insert user userID, err := database.InsertUser(database.DB, u.Username, string(passwordHash)) if err != nil { return err } // Generate token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": userID, "username": u.Username, }) signedToken, err := token.SignedString([]byte(os.Getenv("JWT_SECRET"))) if err != nil { return utils.NewError(utils.ErrInternal, "Failed to generate auth token", err) } // Set token cookie tokenCookie := new(fiber.Cookie) tokenCookie.Name = "token" tokenCookie.Value = signedToken tokenCookie.Expires = time.Now().Add(30 * 24 * time.Hour) c.Cookie(tokenCookie) return c.Status(fiber.StatusOK).JSON(fiber.Map{ "message": "Successfully signed up", "username": u.Username, "user_id": userID, }) } func Login(c *fiber.Ctx) error { type loginStruct struct { Username string `json:"username" xml:"username" form:"username"` Password string `json:"password" xml:"password" form:"password"` } u := new(loginStruct) if err := c.BodyParser(u); err != nil { return utils.NewError(utils.ErrInvalidInput, "Invalid request body", err) } // Validate input if u.Username == "" { return utils.NewError(utils.ErrInvalidInput, "Username is empty", nil) } if u.Password == "" { return utils.NewError(utils.ErrInvalidInput, "Password is empty", nil) } if !utils.IsValidUsername(u.Username) { return utils.NewError(utils.ErrInvalidInput, "Invalid username", nil) } if !utils.IsValidPassword(u.Password) { return utils.NewError(utils.ErrInvalidInput, "Invalid password", nil) } // Check if user exists exist, err := database.CheckUserExists(database.DB, u.Username) if err != nil { return err } if !exist { return utils.NewError(utils.ErrNotFound, "User does not exist", nil) } // Verify password passwordHash, err := database.GetPasswordHash(database.DB, u.Username) if err != nil { return err } if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(u.Password)); err != nil { return utils.NewError(utils.ErrInvalidInput, "Invalid password", nil) } // Get user ID userID, err := database.GetUserID(database.DB, u.Username) if err != nil { return err } // Generate token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": userID, "username": u.Username, }) signedToken, err := token.SignedString([]byte(os.Getenv("JWT_SECRET"))) if err != nil { return utils.NewError(utils.ErrInternal, "Failed to generate token", err) } // Set token cookie tokenCookie := new(fiber.Cookie) tokenCookie.Name = "token" tokenCookie.Value = signedToken tokenCookie.Expires = time.Now().Add(30 * 24 * time.Hour) c.Cookie(tokenCookie) return c.Status(fiber.StatusOK).JSON(fiber.Map{ "message": "Successfully logged in", "username": u.Username, "user_id": userID, }) } func ValidateToken(c *fiber.Ctx) error { username, ok := c.Locals("username").(string) if !ok { return utils.NewError(utils.ErrInvalidInput, "Invalid token: missing username", fmt.Errorf("missing username: %v", c.Locals("username"))) } userIDVal := c.Locals("userID") userID, ok := userIDVal.(uuid.UUID) if !ok { return utils.NewError(utils.ErrUnauthorized, "unauthorized", fmt.Errorf("missing/invalid userID type: %T, value: %v\n", userIDVal, userIDVal)) } return c.Status(fiber.StatusOK).JSON(fiber.Map{ "message": "authorized", "username": username, "user_id": userID, }) }