package main import ( "net/http" "os" "sync" "time" "github.com/golang-jwt/jwt/v5" echojwt "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "golang.org/x/time/rate" ) var jwtSecret []byte func init() { secret := os.Getenv("JWT_SECRET") if secret == "" { panic("JWT_SECRET environment variable is required") } jwtSecret = []byte(secret) } func createToken(userID int, username string, isAdmin bool) (string, error) { claims := &Claims{ UserID: userID, Username: username, IsAdmin: isAdmin, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(jwtSecret) } func JWTMiddleware() echo.MiddlewareFunc { return echojwt.WithConfig(echojwt.Config{ NewClaimsFunc: func(c echo.Context) jwt.Claims { return new(Claims) }, SigningKey: jwtSecret, }) } func AdminMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { user, ok := c.Get("user").(*jwt.Token) if !ok { return echo.NewHTTPError(http.StatusUnauthorized, "JWT token missing or invalid") } claims, ok := user.Claims.(*Claims) if !ok { return echo.NewHTTPError(http.StatusUnauthorized, "Failed to parse JWT claims") } if !claims.IsAdmin { return echo.NewHTTPError(http.StatusForbidden, "Access denied: admin rights required") } return next(c) } } } func CustomLogger() echo.MiddlewareFunc { return middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${time_rfc3339} | ${status} | ${latency_human} | ${method} ${uri}\n", }) } type LoginRateLimiter struct { limiters map[string]*rate.Limiter mu sync.Mutex } func NewLoginRateLimiter() *LoginRateLimiter { limiter := &LoginRateLimiter{ limiters: make(map[string]*rate.Limiter), } go func() { ticker := time.NewTicker(10 * time.Minute) defer ticker.Stop() for range ticker.C { limiter.mu.Lock() limiter.limiters = make(map[string]*rate.Limiter) limiter.mu.Unlock() } }() return limiter } func (l *LoginRateLimiter) GetLimiter(ip string) *rate.Limiter { l.mu.Lock() defer l.mu.Unlock() limiter, exists := l.limiters[ip] if !exists { limiter = rate.NewLimiter(rate.Every(time.Minute/5), 5) l.limiters[ip] = limiter } return limiter } func (l *LoginRateLimiter) Middleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ip := c.RealIP() limiter := l.GetLimiter(ip) if !limiter.Allow() { return echo.NewHTTPError(http.StatusTooManyRequests, "Too many login attempts. Please try again later.") } return next(c) } } } func HTTPSRedirectMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if os.Getenv("ENVIRONMENT") == "production" { if c.Request().Header.Get("X-Forwarded-Proto") != "https" { return c.Redirect(http.StatusMovedPermanently, "https://"+c.Request().Host+c.Request().RequestURI) } } return next(c) } } }