package api import ( "context" "fmt" "net/http" "os" "strings" "fr.latosa-escrima/api/users" core "fr.latosa-escrima/core" "fr.latosa-escrima/core/models" "fr.latosa-escrima/utils" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) func CORS(next http.Handler) http.Handler { CORS_AllowOrigin := os.Getenv("CORS_AllowOrigin") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Allow all origins (can restrict to specific origins) w.Header().Set("Access-Control-Allow-Origin", CORS_AllowOrigin) // Allow certain HTTP methods (you can customize these as needed) w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") // Allow certain headers (you can add more as needed) w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-CSRF-Token") w.Header().Set("Access-Control-Allow-Credentials", "true") // Handle OPTIONS pre-flight request if r.Method == http.MethodOptions { return } next.ServeHTTP(w, r) }) } func AuthJWT(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Check if the Authorization header is provided authHeader := r.Header.Get("Authorization") if authHeader == "" { core.JSONError{ Status: core.Error, Message: "Missing Authorization header", }.Respond(w, http.StatusUnauthorized) return } // Bearer token is expected, so split the header into "Bearer " tokenString := strings.TrimPrefix(authHeader, "Bearer ") if tokenString == authHeader { core.JSONError{ Status: core.Error, Message: "Invalid Authorization header format", }.Respond(w, http.StatusUnauthorized) return } // Parse the token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // Ensure that the token's signing method is valid if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return users.MySigningKey, nil }) if err != nil || !token.Valid { core.JSONError{ Status: core.Error, Message: "Invalid Token", }.Respond(w, http.StatusUnauthorized) return } ctx := context.WithValue(r.Context(), "token", token) // Call the next handler if the JWT is valid next.ServeHTTP(w, r.WithContext(ctx)) }) } // @param methods string -> HttpMethods separated by commas. func Methods(methods ...string) core.Middleware { // _methods := strings.Split(strings.ToUpper(methods), ",") middleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if len(methods) > 0 { if !utils.Contains(methods, r.Method) { core.JSONError{ Status: core.Error, Message: "Method is not allowed.", }.Respond(w, http.StatusMethodNotAllowed) return } } next.ServeHTTP(w, r) }) } return middleware } // WARNING: For this to work, the Middleware AuthJWT has to be applied after in // the Middleswares ([]core.Middleware) // // This Middleware behaves like a AND on the actions. The user must have all the // listed actions on this particular resource. func HasPermissions(resource string, actions ...string) core.Middleware { var middleware core.Middleware = func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context.Background() token, ok := r.Context().Value("token").(*jwt.Token) if !ok { core.JSONError{ Status: core.Error, Message: "Couldn't retrieve your JWT.", }.Respond(w, http.StatusInternalServerError) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { core.JSONError{ Status: core.Error, Message: "Invalid token claims.", }.Respond(w, http.StatusInternalServerError) return } id, err := uuid.Parse(claims["user_id"].(string)) if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } var user models.User count, err := core.DB.NewSelect().Model(&user). Where("user_id = ?", id). Relation("Roles.Permissions"). Limit(1). ScanAndCount(ctx) if count == 0 { core.JSONError{ Status: core.Error, Message: "The user doesn't exist", }.Respond(w, http.StatusNotFound) return } if err != nil { core.JSONError{ Status: core.Error, Message: err.Error(), }.Respond(w, http.StatusInternalServerError) return } // permissions := utils.MergeArrays( // utils.Map(user.Roles, func(r models.Role) []models.Permission { // return r.Permissions // })...) // // for _, action := range actions { // permission := utils.Find(permissions, func(p models.Permission, i int) bool { // return resource == p.Resource && action == p.Action // }) // if permission == nil { // core.JSONError{ // Status: core.Error, // Message: fmt.Sprintf("The user doesn't have the proper permission %s:%s", resource, action), // }.Respond(w, http.StatusUnauthorized) // return // } // } permissionsSet := make(map[string]struct{}) // Set to store unique permissions // Populate the set with user's permissions for _, role := range user.Roles { for _, perm := range role.Permissions { key := perm.Resource + ":" + perm.Action permissionsSet[key] = struct{}{} // Store as a set for fast lookups } } // Check if the user has all required permissions for _, action := range actions { key := resource + ":" + action if _, exists := permissionsSet[key]; !exists { core.JSONError{ Status: core.Error, Message: fmt.Sprintf("The user doesn't have the proper permission %s:%s", resource, action), }.Respond(w, http.StatusUnauthorized) return } } next.ServeHTTP(w, r) }) } return middleware }