Initial commit

This commit is contained in:
Lizandro Guarnizo
2025-02-06 14:22:29 -05:00
commit 6d8f1bcd6f
452 changed files with 250835 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
package middlewares
import (
"github.com/gofiber/fiber/v2"
"github.com/gookit/validate"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
func ValidateApiLoginPost(c *fiber.Ctx) error {
var login models.Login
if err := c.BodyParser(&login); err != nil {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": "Invalid Credentials",
})
}
v := validate.Struct(login)
if !v.Validate() {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": "Invalid Credentials",
})
}
user, err := login.CheckLogin() //nolint:wsl
if err != nil {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": "Invalid Credentials",
})
}
c.Locals("user", user)
return c.Next()
}
+27
View File
@@ -0,0 +1,27 @@
package middlewares
import (
"github.com/gofiber/fiber/v2"
"github.com/gookit/validate"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
func ValidateApiRegisterPost(c *fiber.Ctx) error {
var register models.RegisterForm
if err := c.BodyParser(&register); err != nil {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": err.Error(),
})
}
v := validate.Struct(register)
if !v.Validate() {
return c.Status(401).JSON(fiber.Map{
"error": true,
"message": v.Errors,
})
}
c.Locals("register", register)
return c.Next()
}
+272
View File
@@ -0,0 +1,272 @@
package middlewares
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/oarkflow/log"
"github.com/sujit-baniya/fiber-boilerplate/app"
config2 "github.com/sujit-baniya/fiber-boilerplate/config"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/form3tech-oss/jwt-go"
"github.com/gofiber/fiber/v2"
)
// Config defines the config for BasicAuth middleware
type AuthConfig struct {
// Filter defines a function to skip middleware.
// Optional. Default: nil
Filter func(*fiber.Ctx) bool
// SuccessHandler defines a function which is executed for a valid token.
// Optional. Default: nil
SuccessHandler fiber.Handler
// ErrorHandler defines a function which is executed for an invalid token.
// It may be used to define a custom JWT error.
// Optional. Default: 401 Invalid or expired JWT
ErrorHandler fiber.ErrorHandler
// Signing key to validate token. Used as fallback if SigningKeys has length 0.
// Required. This or SigningKeys.
SigningKey interface{}
// Map of signing keys to validate token with kid field usage.
// Required. This or SigningKey.
SigningKeys map[string]interface{}
// Signing method, used to check token signing method.
// Optional. Default: "HS256".
// Possible values: "HS256", "HS384", "HS512", "ES256", "ES384", "ES512", "RS256", "RS384", "RS512"
SigningMethod string
// Context key to store user information from the token into context.
// Optional. Default: "user".
ContextKey string
// Claims are extendable claims data defining token content.
// Optional. Default value jwt.MapClaims
Claims jwt.Claims
// TokenLookup is a string in the form of "<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
TokenLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default: "Bearer".
AuthScheme string
keyFunc jwt.Keyfunc
}
// New ...
func Authenticate(config ...AuthConfig) fiber.Handler {
// Init config
var cfg AuthConfig
if len(config) > 0 {
cfg = config[0]
}
if cfg.SuccessHandler == nil {
cfg.SuccessHandler = func(c *fiber.Ctx) error {
return c.Next()
}
}
if cfg.ErrorHandler == nil {
cfg.ErrorHandler = func(c *fiber.Ctx, err error) error {
var er fiber.Error
if err.Error() == "Missing or malformed JWT" {
er.Code = fiber.StatusBadRequest
} else {
er.Code = fiber.StatusUnauthorized
}
er.Message = err.Error()
return config2.CustomErrorHandler(c, &er)
}
}
if cfg.SigningKey == nil && len(cfg.SigningKeys) == 0 {
log.Error().Msg("Fiber: JWT middleware requires signing key")
}
if cfg.SigningMethod == "" {
cfg.SigningMethod = "HS256"
}
if cfg.ContextKey == "" {
cfg.ContextKey = "user"
}
if cfg.Claims == nil {
cfg.Claims = jwt.MapClaims{}
}
if cfg.TokenLookup == "" {
cfg.TokenLookup = "header:" + fiber.HeaderAuthorization
}
if cfg.AuthScheme == "" {
cfg.AuthScheme = "Bearer"
}
cfg.keyFunc = func(t *jwt.Token) (interface{}, error) {
// Check the signing method
if t.Method.Alg() != cfg.SigningMethod {
return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"])
}
if len(cfg.SigningKeys) > 0 {
if kid, ok := t.Header["kid"].(string); ok {
if key, ok := cfg.SigningKeys[kid]; ok {
return key, nil
}
}
return nil, fmt.Errorf("Unexpected jwt key id=%v", t.Header["kid"])
}
return cfg.SigningKey, nil
}
// Initialize
extractors := make([]func(c *fiber.Ctx) (string, error), 0)
rootParts := strings.Split(cfg.TokenLookup, ",")
for _, rootPart := range rootParts {
parts := strings.Split(strings.TrimSpace(rootPart), ":")
switch parts[0] {
case "header":
extractors = append(extractors, jwtFromHeader(parts[1], cfg.AuthScheme))
case "query":
extractors = append(extractors, jwtFromQuery(parts[1]))
case "param":
extractors = append(extractors, jwtFromParam(parts[1]))
case "cookie":
extractors = append(extractors, jwtFromCookie(parts[1]))
}
}
// Return middleware handler
return func(c *fiber.Ctx) error {
// Filter request to skip middleware
if cfg.Filter != nil && cfg.Filter(c) {
return c.Next()
}
var auth string
var err error
for _, extractor := range extractors {
auth, err = extractor(c)
if auth != "" && err == nil {
break
}
}
if err != nil {
return cfg.ErrorHandler(c, err)
}
token := new(jwt.Token)
if _, ok := cfg.Claims.(jwt.MapClaims); ok {
token, err = jwt.Parse(auth, cfg.keyFunc)
} else {
t := reflect.ValueOf(cfg.Claims).Type().Elem()
claims := reflect.New(t).Interface().(jwt.Claims)
token, err = jwt.ParseWithClaims(auth, claims, cfg.keyFunc)
}
if err == nil && token.Valid {
// Store user information from token into context.
c.Locals(cfg.ContextKey, token)
return cfg.SuccessHandler(c)
}
return cfg.ErrorHandler(c, err)
}
}
// jwtFromHeader returns a function that extracts token from the request header.
func jwtFromHeader(header string, authScheme string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
auth := c.Get(header)
l := len(authScheme)
if len(auth) > l+1 && auth[:l] == authScheme {
return auth[l+1:], nil
}
return "", errors.New("Missing or malformed JWT")
}
}
// jwtFromQuery returns a function that extracts token from the query string.
func jwtFromQuery(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Query(param)
if token == "" {
return "", errors.New("Missing or malformed JWT")
}
return token, nil
}
}
// jwtFromParam returns a function that extracts token from the url param string.
func jwtFromParam(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Params(param)
if token == "" {
return "", errors.New("Missing or malformed JWT")
}
return token, nil
}
}
// jwtFromCookie returns a function that extracts token from the named cookie.
func jwtFromCookie(name string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
token := c.Cookies(name)
if token == "" {
return "", errors.New("Missing or malformed JWT")
}
return token, nil
}
}
func AuthWeb() func(*fiber.Ctx) error {
// Definir la función que maneja la autenticación
return func(c *fiber.Ctx) error {
// Intentar obtener el usuario
user, err := auth.User(c)
// Si no se puede obtener el usuario (es nil o hay error), hacer logout
if err != nil || user == nil {
// Ejecutar logout y destruir la sesión
auth.Logout(c)
// Redirigir al login
return c.Redirect("/login")
}
// Si el usuario está presente, continuar con la autenticación normal
return Authenticate(AuthConfig{
SigningKey: []byte(app.Http.Token.AppJwtSecret),
TokenLookup: "cookie:Verify-Rest-Token",
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
// En caso de error de autenticación, hacer logout
auth.Logout(ctx)
return ctx.Redirect("/login")
},
})(c) // Llamar al siguiente middleware
}
}
func AuthAdmin(c *fiber.Ctx) error {
if !auth.IsAdmin(c) {
auth.Logout(c)
return c.Redirect("/login")
}
return c.Next()
}
func AuthApi() func(*fiber.Ctx) error {
return Authenticate(AuthConfig{
SigningKey: []byte(app.Http.Token.ApiJwtSecret),
TokenLookup: "header:Verify-Rest-Token",
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
auth.Logout(ctx)
return ctx.Status(401).JSON("Invalid Attempt")
},
})
}
+30
View File
@@ -0,0 +1,30 @@
package middlewares
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
)
func LoadUserMiddleware(c *fiber.Ctx) error {
user, err := auth.User(c)
if err == nil && user != nil {
// Convertimos el usuario a un map[string]interface{} e incluimos los campos necesarios
userData := map[string]interface{}{
"ID": user.ID,
"Name": user.Name,
"Email": user.Email,
"NombreUsuario": user.NombreUsuario,
"IsAdmin": user.IsAdmin,
"Role": map[string]interface{}{
"ID": user.Role.ID,
"Name": user.Role.Name,
// Incluye otros campos relevantes del rol
},
}
// Guarda el map en el contexto
c.Locals("user", userData)
}
// Si no está autenticado, continuamos sin agregar nada
return c.Next()
}
+11
View File
@@ -0,0 +1,11 @@
package middlewares
import "github.com/gofiber/fiber/v2"
func LoadCacheHeaders(c *fiber.Ctx) error {
c.Set("X-DNS-Prefetch-Control", "off")
c.Set("Pragma", "no-cache")
c.Set("Expires", "Fri, 01 Jan 1990 00:00:00 GMT")
c.Set("Cache-Control", "no-cache, must-revalidate, no-store, max-age=0, private")
return c.Next()
}
+13
View File
@@ -0,0 +1,13 @@
package middlewares
import (
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
)
func LimitPhoneNumbersPerRequest(c *fiber.Ctx) error {
if auth.IsLoggedIn(c) {
return c.Redirect("/")
}
return c.Next()
}
+47
View File
@@ -0,0 +1,47 @@
package middlewares
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/oarkflow/log"
)
func NewLog(logger *log.Logger, skip func(c *fiber.Ctx) bool) fiber.Handler {
return func(c *fiber.Ctx) error {
start := time.Now()
next := c.Next()
if skip != nil && skip(c) {
return nil
}
end := time.Now()
latency := end.Sub(start)
status := c.Response().StatusCode()
msg := "Request"
if next != nil {
msg = next.Error()
}
var e *log.Entry
switch {
case status >= 400 && status < 500:
e = logger.Warn()
case status >= 500:
e = logger.Error()
default:
e = logger.Info()
}
e.Int("status", status).
Str("method", c.Method()).
Str("path", c.Path()).
Str("ip", c.IP()).
Dur("latency", latency).
Str("user_agent", c.Get(fiber.HeaderUserAgent)).
Msg(msg)
return nil
}
}
+44
View File
@@ -0,0 +1,44 @@
package middlewares
import (
"github.com/gofiber/fiber/v2"
"github.com/gookit/validate"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
func RedirectToHomePageOnLogin(c *fiber.Ctx) error {
if auth.IsLoggedIn(c) {
return c.Redirect("/app/dashboard")
}
return c.Next()
}
func ValidateLoginPost(c *fiber.Ctx) error {
var login models.Login
if err := c.BodyParser(&login); err != nil {
return c.JSON(fiber.Map{
"status": "error",
"message": err.Error(),
})
}
v := validate.Struct(login)
if !v.Validate() {
return c.JSON(fiber.Map{
"status": "error",
"message": v.Errors.One(),
})
}
user, err := login.CheckLogin()
if err != nil {
return c.JSON(fiber.Map{
"status": "error",
"message": err.Error(),
})
}
c.Locals("user", user)
return c.Next()
}
+115
View File
@@ -0,0 +1,115 @@
package middlewares
import (
"fmt"
"sort"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
func MenuMiddleware(c *fiber.Ctx) error {
// Verificar si el usuario está logueado
if !auth.IsLoggedIn(c) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": true,
"message": "Usuario no autenticado",
})
}
// Obtener los datos del usuario autenticado
user, err := auth.User(c)
if err != nil {
// Si ocurre un error al obtener el usuario, finalizar la solicitud
fmt.Printf("Error al obtener usuario: %v\n", err)
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": true,
"message": "Usuario no válido o sesión expirada",
})
}
// Buscar los módulos y submódulos del usuario
userModules, err := models.FindUserByID(user.ID)
if err != nil {
fmt.Printf("Error al buscar datos del usuario: %v\n", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"message": "Error al obtener datos del usuario",
})
}
// Organizar los módulos y submódulos
moduleMap := make(map[string][]map[string]string)
for _, submodule := range userModules.Role.Submodules {
// Recuperar el nombre del módulo
moduleName := submodule.Module.Title
// Crear un mapa para cada submódulo que contenga el título y la URL
submoduleData := map[string]string{
"title": submodule.Title,
"url": submodule.Url, // Suponiendo que el submódulo tiene un campo `URL`
}
// Agregar el submódulo a la lista correspondiente del módulo
moduleMap[moduleName] = append(moduleMap[moduleName], submoduleData)
}
// Ordenar los módulos alfabéticamente
var sortedModules []string
for module := range moduleMap {
sortedModules = append(sortedModules, module)
}
sort.Strings(sortedModules)
// Ordenar los submódulos de cada módulo alfabéticamente
for _, moduleName := range sortedModules {
// Ordenar los submódulos alfabéticamente por título
sort.Slice(moduleMap[moduleName], func(i, j int) bool {
return moduleMap[moduleName][i]["title"] < moduleMap[moduleName][j]["title"]
})
}
c.Locals("userModules", moduleMap)
// Obtener un array solo con las URLs
var urls []string
for _, submodules := range moduleMap {
for _, submoduleData := range submodules {
urls = append(urls, submoduleData["url"])
}
}
// Opcional: adjuntar la lista de URLs al contexto
c.Locals("urls", urls)
// Verificar si la URL de la solicitud está en la lista de URLs permitidas
requestURL := c.Path()
// Obtener el índice del último '/' en la URL de la solicitud
lastSlashIndex := strings.LastIndex(requestURL, "/")
if lastSlashIndex != -1 {
// Obtener solo la parte de la URL después del último '/'
requestURL = requestURL[lastSlashIndex+1:]
}
// Verificar si la URL modificada está en la lista de URLs permitidas
urlAllowed := false
for _, url := range urls {
// Comparar solo la parte de la URL después del último '/'
if requestURL == url[strings.LastIndex(url, "/")+1:] {
urlAllowed = true
break
}
}
// Si la URL no está permitida, devolver un error
if !urlAllowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": true,
"message": "Acceso no permitido a esta ruta",
})
}
// Continuar con la siguiente función de middleware o manejador
return c.Next()
}
+58
View File
@@ -0,0 +1,58 @@
package middlewares
import (
"errors"
"strconv"
"strings"
"time"
"github.com/sujit-baniya/fiber-boilerplate/utils"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/app"
)
func ValidatePasswordReset(c *fiber.Ctx) error {
token := c.Query("t")
err := _validatePasswordReset(c, token)
if err != nil {
return app.Http.Flash.WithError(c, fiber.Map{
"message": err.Error(),
}).Redirect("/token-vencido")
}
return c.Next()
}
func ValidatePasswordResetPost(c *fiber.Ctx) error {
token := c.Params("token")
err := _validatePasswordReset(c, token)
if err != nil {
return app.Http.Flash.WithError(c, fiber.Map{
"message": err.Error(),
}).Redirect("/login")
}
return c.Next()
}
func _validatePasswordReset(c *fiber.Ctx, t string) error {
t = utils.Decrypt(t, app.Http.Server.Key)
emailParts := strings.Split(t, "-reset-")
if len(emailParts) != 2 {
return errors.New("Invalid Password Reset Token")
}
tokenTS, err := strconv.ParseInt(emailParts[1], 10, 64)
if err != nil {
return errors.New("Invalid Password Reset Token")
}
now := time.Now().Unix()
diff := now - tokenTS
if diff > (5 * 60) {
return errors.New("Password Reset Token has expired!")
} else if diff < 0 {
return errors.New("Invalid Password Reset Token")
}
c.Locals("email", emailParts[0])
c.Locals("token", t)
return nil
}
+69
View File
@@ -0,0 +1,69 @@
package middlewares
import (
"fmt"
"github.com/sujit-baniya/fiber-boilerplate/utils"
"github.com/gofiber/fiber/v2"
"github.com/gookit/validate"
"github.com/sujit-baniya/fiber-boilerplate/app"
"github.com/sujit-baniya/fiber-boilerplate/pkg/auth"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
func ValidateRegisterPost(c *fiber.Ctx) error {
// Declara el modelo RegisterForm
var register models.RegisterForm
// Intenta parsear el cuerpo de la solicitud
if err := c.BodyParser(&register); err != nil {
return app.Http.Flash.WithError(c, fiber.Map{
"message": "Error al procesar los datos del formulario: " + err.Error(),
}).Redirect("/register-gas")
}
// Realiza la validación del formulario
v := validate.Struct(register)
if v == nil {
return app.Http.Flash.WithError(c, fiber.Map{
"message": "Error en la validación de los datos del formulario.",
}).Redirect("/register-gas")
}
// Verifica si hay errores de validación
if !v.Validate() {
errorMessage := "Error desconocido en los datos del formulario."
if len(v.Errors) > 0 {
errorMessage = v.Errors.One() // Obtiene el primer error si existe
}
return app.Http.Flash.WithError(c, fiber.Map{
"message": errorMessage,
}).Redirect("/register-gas")
}
return c.Next()
}
func ValidateConfirmToken(c *fiber.Ctx) error {
t := utils.Decrypt(c.Query("t"), app.Http.Server.Key)
fmt.Println(t)
user, err := models.GetUserByUsuarioVerify(t)
if err != nil {
return app.Http.Flash.WithError(c, fiber.Map{
"message": err.Error(),
}).Redirect("/login")
}
if user.EmailVerified {
return app.Http.Flash.WithError(c, fiber.Map{
"message": "Usuario was already validated",
}).Redirect("/login")
}
user.EmailVerified = true
app.Http.Database.DB.Save(&user)
auth.Login(c, user.ID, app.Http.Server.Key) //nolint:wsl
c.Locals("user", user)
return c.Next()
}
+31
View File
@@ -0,0 +1,31 @@
package middlewares
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"time"
)
func MaxBodySize(sizeInMB int) fiber.Handler {
sizeInMB = sizeInMB * 1024 * 1024
return func(c *fiber.Ctx) error {
if len(c.Body()) >= sizeInMB {
// custom response here
return fiber.ErrRequestEntityTooLarge
}
return c.Next()
}
}
func Limit(maxRequest int, duration time.Duration) func(*fiber.Ctx) error {
return limiter.New(limiter.Config{
Max: maxRequest,
Expiration: duration * time.Minute,
LimitReached: func(c *fiber.Ctx) error {
return c.Status(429).JSON(fiber.Map{
"error": true,
"message": "Too many requests",
})
},
})
}
+86
View File
@@ -0,0 +1,86 @@
// scrypt.go
package middlewares
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"time"
"golang.org/x/crypto/scrypt"
)
func Encrypt(key, data []byte) ([]byte, error) {
key, salt, err := DeriveKey(key, nil)
if err != nil {
return nil, err
}
blockCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = rand.Read(nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
ciphertext = append(ciphertext, salt...)
return ciphertext, nil
}
func Decrypt(key, data []byte) ([]byte, error) {
salt, data := data[len(data)-32:], data[:len(data)-32]
key, _, err := DeriveKey(key, salt)
if err != nil {
return nil, err
}
blockCipher, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
func DeriveKey(password, salt []byte) ([]byte, []byte, error) {
if salt == nil {
salt = make([]byte, 32)
if _, err := rand.Read(salt); err != nil {
return nil, nil, err
}
}
key, err := scrypt.Key(password, salt, 1048576, 8, 1, 32)
if err != nil {
return nil, nil, err
}
return key, salt, nil
}
func main() {
var (
password = []byte("mysecretpassword")
data = []byte(fmt.Sprintf("%v", time.Now().Unix()))
)
ciphertext, err := Encrypt(password, data)
if err != nil {
log.Fatal(err)
}
fmt.Printf("ciphertext: %s\n", hex.EncodeToString(ciphertext))
plaintext, err := Decrypt(password, ciphertext)
if err != nil {
log.Fatal(err)
}
fmt.Printf("plaintext: %s\n", plaintext)
}