From 977e1c5388b36386ac931e787cd3dd2e0d20f359 Mon Sep 17 00:00:00 2001 From: marsiandeployer Date: Mon, 23 Mar 2026 20:14:13 +0300 Subject: [PATCH] fix: add missing middleware, .env.example, fix .gitignore binary patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Recovery(), CORSMiddleware(), SecurityHeadersMiddleware() — declared in main.go but files were missing, causing build failure - Add frontend/.env.example so new devs know what variables are required - Fix back-end/.gitignore: binary names (api, verify, worker) were matching the cmd/api/ directory and hiding source files from git; changed to /api etc. How to test: cd back-end && go build ./... # should succeed with exit 0 cd frontend && flutter build web # requires .env (copy from .env.example) --- back-end/.gitignore | 10 +++--- back-end/cmd/api/middleware/cors.go | 33 +++++++++++++++++++ back-end/cmd/api/middleware/recovery.go | 21 ++++++++++++ .../cmd/api/middleware/security_headers.go | 15 +++++++++ frontend/.env.example | 3 ++ 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 back-end/cmd/api/middleware/cors.go create mode 100644 back-end/cmd/api/middleware/recovery.go create mode 100644 back-end/cmd/api/middleware/security_headers.go create mode 100644 frontend/.env.example diff --git a/back-end/.gitignore b/back-end/.gitignore index 1d102903..808e9f55 100644 --- a/back-end/.gitignore +++ b/back-end/.gitignore @@ -4,11 +4,11 @@ ! .env.example # 🏗️ Build Binaries (Go) -api -api_server -verify -verify_transfer -worker +/api +/api_server +/verify +/verify_transfer +/worker /bin/ *.exe *.exe~ diff --git a/back-end/cmd/api/middleware/cors.go b/back-end/cmd/api/middleware/cors.go new file mode 100644 index 00000000..9b1551fa --- /dev/null +++ b/back-end/cmd/api/middleware/cors.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "os" + + "github.com/gin-gonic/gin" +) + +// CORSMiddleware sets CORS headers. Allowed origin is read from CORS_ORIGIN env var, +// defaults to the production domain. +func CORSMiddleware() gin.HandlerFunc { + allowedOrigin := os.Getenv("CORS_ORIGIN") + if allowedOrigin == "" { + allowedOrigin = "https://paycif.com" + } + + return func(c *gin.Context) { + origin := c.GetHeader("Origin") + if origin == allowedOrigin || allowedOrigin == "*" { + c.Header("Access-Control-Allow-Origin", origin) + } + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Device-Id, X-Device-Signature, X-Request-ID") + c.Header("Access-Control-Max-Age", "86400") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} diff --git a/back-end/cmd/api/middleware/recovery.go b/back-end/cmd/api/middleware/recovery.go new file mode 100644 index 00000000..b3fc05c5 --- /dev/null +++ b/back-end/cmd/api/middleware/recovery.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "log/slog" + "net/http" + + "github.com/gin-gonic/gin" +) + +// Recovery returns a middleware that recovers from panics and returns 500. +func Recovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + slog.Error("Panic recovered", "error", err, "path", c.Request.URL.Path) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"}) + } + }() + c.Next() + } +} diff --git a/back-end/cmd/api/middleware/security_headers.go b/back-end/cmd/api/middleware/security_headers.go new file mode 100644 index 00000000..538e2ca9 --- /dev/null +++ b/back-end/cmd/api/middleware/security_headers.go @@ -0,0 +1,15 @@ +package middleware + +import "github.com/gin-gonic/gin" + +// SecurityHeadersMiddleware adds standard security headers to every response. +func SecurityHeadersMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Header("X-Content-Type-Options", "nosniff") + c.Header("X-Frame-Options", "DENY") + c.Header("X-XSS-Protection", "1; mode=block") + c.Header("Referrer-Policy", "strict-origin-when-cross-origin") + c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()") + c.Next() + } +} diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 00000000..1d75d504 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,3 @@ +BACKEND_URL=https://api.paycif.com +SUPABASE_URL=https://your-project.supabase.co +SUPABASE_ANON_KEY=your_anon_key_here