Add initial backend implementation
- Go module with Gin, GORM, JWT, excelize dependencies - Configuration loading from YAML with all settings - GORM models for users, categories, components, configurations, alerts - Repository layer for all entities - Services: auth (JWT), pricing (median/average/weighted), components, quotes, configurations, export (CSV/XLSX), alerts - Middleware: JWT auth, role-based access, CORS - HTTP handlers for all API endpoints - Main server with dependency injection and graceful shutdown Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
101
internal/middleware/auth.go
Normal file
101
internal/middleware/auth.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mchus/quoteforge/internal/models"
|
||||
"github.com/mchus/quoteforge/internal/services"
|
||||
)
|
||||
|
||||
const (
|
||||
AuthUserKey = "auth_user"
|
||||
AuthClaimsKey = "auth_claims"
|
||||
)
|
||||
|
||||
func Auth(authService *services.AuthService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "authorization header required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "invalid authorization header format",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authService.ValidateToken(parts[1])
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set(AuthClaimsKey, claims)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func RequireRole(roles ...models.UserRole) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
claims, exists := c.Get(AuthClaimsKey)
|
||||
if !exists {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "authentication required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
authClaims := claims.(*services.Claims)
|
||||
|
||||
for _, role := range roles {
|
||||
if authClaims.Role == role {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "insufficient permissions",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RequireEditor() gin.HandlerFunc {
|
||||
return RequireRole(models.RoleEditor, models.RolePricingAdmin, models.RoleAdmin)
|
||||
}
|
||||
|
||||
func RequirePricingAdmin() gin.HandlerFunc {
|
||||
return RequireRole(models.RolePricingAdmin, models.RoleAdmin)
|
||||
}
|
||||
|
||||
func RequireAdmin() gin.HandlerFunc {
|
||||
return RequireRole(models.RoleAdmin)
|
||||
}
|
||||
|
||||
// GetClaims extracts auth claims from context
|
||||
func GetClaims(c *gin.Context) *services.Claims {
|
||||
claims, exists := c.Get(AuthClaimsKey)
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return claims.(*services.Claims)
|
||||
}
|
||||
|
||||
// GetUserID extracts user ID from context
|
||||
func GetUserID(c *gin.Context) uint {
|
||||
claims := GetClaims(c)
|
||||
if claims == nil {
|
||||
return 0
|
||||
}
|
||||
return claims.UserID
|
||||
}
|
||||
22
internal/middleware/cors.go
Normal file
22
internal/middleware/cors.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CORS() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Content-Disposition")
|
||||
c.Header("Access-Control-Max-Age", "86400")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user