Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 [Feature]: Get Route Name/Path inside Middlewares #2195

Open
3 tasks done
mirusky opened this issue Nov 5, 2022 · 14 comments
Open
3 tasks done

🚀 [Feature]: Get Route Name/Path inside Middlewares #2195

mirusky opened this issue Nov 5, 2022 · 14 comments

Comments

@mirusky
Copy link
Contributor

mirusky commented Nov 5, 2022

Feature Description

Expose the route name inside middleware before reach the end handler / last handler in the stack.

Additional Context (optional)

Currently I'm trying to implement a RBAC system, each route has a name defined like "resource.action" eg: user.create, user.read...

And the idea is provide an API where user can change the necessary permissions for each route. Like: the route called user.create is necessary to be have admin or user:create or user:* permission.

So the Middleware will check the current route name and validate if the logged user has the appropriate permissions.

Some time ago I had seen a similar issue, but I remember that it was mentioned that the way the stack is built would have to be modified. I didn't found the Issue to put it here...

EDIT:
Related Issues #1334 #923

Code Snippet (optional)

package main

import (
	"fmt"

	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()
	// app.Use(logger.New())
	// app.Use(cors.New())

	app.Use(func(c *fiber.Ctx) error {

		fmt.Printf("%+v\n", c.Route().Name) // Prints ""

		err := c.Next()

		fmt.Printf("%+v\n", c.Route().Name) // Prints "Ping"
		return err
	})

	app.Get("/ping", Ping).Name("Ping")

	app.Listen(":3000")
}

func Ping(c *fiber.Ctx) error {
	return c.SendString("Pong!")
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have checked for existing issues that describe my suggestion prior to opening this one.
  • I understand that improperly formatted feature requests may be closed without explanation.
@ReneWerner87
Copy link
Member

Hi, so you are suggesting that we somehow figure out the next route before it gets to this one? I.e. we would have to go through all declared routes and match them with the current url which would be quite unperformant.
Furthermore there is the possibility to adjust the current path within the middleware, so that it would have to be parsed again.

Would be all very unperformant, currently we check one route after the other in the sequence as it was registered additionally with a few tricks

The result of the routes that match could then contain multiple routes, as each middleware has a path and there is no distinction between these and the end handlers.

I could only imagine to provide a functionality which is executed outside the internal process and finds out the routes, so that not every request and every user of the framework has to be exposed to the overhead.

@mirusky
Copy link
Contributor Author

mirusky commented Nov 5, 2022

Hi @ReneWerner87 thanks for the explanation, yeah I was imagining that was a performance problem to implement that and also the logic to figure out the route is not simple.

Probably I'll do it in another way, I saw that routes are exposed in app.Stack() probably I'll iterate it and create kind of a manager.

@mirusky mirusky closed this as completed Nov 5, 2022
@li-jin-gou
Copy link
Contributor

Is it a method like access path and HandlerName? Similar to ctx.HandlerName or ctx.Path?

@ReneWerner87
Copy link
Member

If you get it working, let us know maybe this is important for others too

Otherwise we have to look again and try to extend the framework

@li-jin-gou
Copy link
Contributor

li-jin-gou commented Nov 5, 2022

If you get it working, let us know maybe this is important for others too

Otherwise we have to look again and try to extend the framework

why not use ctx.Route().Name to get route name?😳

Having understood the problem, can we provide ctx.HandlerName instead of ctx.Route().Name, similar to Hertz's processing logic. I think handler name and route name are the same

refer to https://github.com/cloudwego/hertz/blob/develop/pkg/app/context.go#L634

@mirusky
Copy link
Contributor Author

mirusky commented Nov 6, 2022

If you get it working, let us know maybe this is important for others too

Otherwise we have to look again and try to extend the framework

Currently I've created kind of a router manager, after the app starts it register all routes names and methods.

type Manager interface{
  Add(Route)
  Get(name, method string) Route
  SetPermissions(name, method string, permissions []string)
}

type Route struct{
 Name string
 Method string
 Permissions string
}

func register(app *fiber.App) {
 for _, routes := range app.Stack() {
  for _, route := range routes {
    Manager.Add(Route{Name: route.Name, Method: route.Method})
  }
 }
}

So then I could use that manager like:

package main

import (
	"fmt"

	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()
	// app.Use(logger.New())
	// app.Use(cors.New())

	app.Use(func(c *fiber.Ctx) error {

		fmt.Printf("%+v\n", c.Route().Name) // Prints ""

		err := c.Next()

		fmt.Printf("%+v\n", c.Route().Name) // Prints "Ping"
		return err
	})

	app.Get("/ping", Ping).Name("Ping")

	register(app)

	app.Listen(":3000")
}

func Ping(c *fiber.Ctx) error {
	route := Manager().Get(c.Route().Name, c.Route().Method)
	// Now here I could get the route permissions using route.Permissions and check if user has the permissions. 

	return c.SendString("Pong!")
}

Of course it's not the best approach since inside every route has a boilerplate code, would be nice to have the information of the currently route being called inside middleware so I could put the logic there.

@mirusky
Copy link
Contributor Author

mirusky commented Nov 6, 2022

Maybe we could rethink the app stack logic to v3? @ReneWerner87 @li-jin-gou

@mirusky
Copy link
Contributor Author

mirusky commented Nov 6, 2022

why not use ctx.Route().Name to get route name?😳

Having understood the problem, can we provide ctx.HandlerName instead of ctx.Route().Name, similar to Hertz's processing logic. I think handler name and route name are the same

refer to https://github.com/cloudwego/hertz/blob/develop/pkg/app/context.go#L634

The Hertz's HandlerName() is not the same as c.Route().Name, look at the code docs:

For example if the handler is "handleGetUsers()", this function will return "main.handleGetUsers".

So for example if we implement the same logic as Hertz's it'll return the handler function name not the actual name defined by user:

app.Get("/ping", Ping).Name("Pong") // It will return `main.Ping` not `Pong`

Actually the problem is that we don't know the route name before reaching it (as I tested). So in a middleware when we call c.Route().Name it returns "" (empty).

Just question, is it possible to get the last handler in the chain ? Maybe in that way we could get the actual c.Route().Name?

@mirusky
Copy link
Contributor Author

mirusky commented Nov 20, 2022

I accidentally closed it, still need a better way to get the route name... The workaround I mentioned earlier creates a lot of boilerplate code.

Ideally, it would be nice to have the current route name inside the middleware, so that way I only have to worry about one piece of code, not many parts.

@mirusky mirusky reopened this Nov 20, 2022
@hmmftg
Copy link

hmmftg commented Feb 6, 2023

Hi
Is this feature listed in V3? or there is no sign of hope?
We've migrated from gin and the only thing that forces us to duplicates our functions to: fx_gin() and fx_fiber() is this issue 😞

@ReneWerner87
Copy link
Member

We can also develop a feature for v2, but this must be optional and unlike now, could check all routes for the request url

In v3 it should be possible in any case, since you can then exchange your router there

@utherbit
Copy link

utherbit commented Aug 7, 2023

Hello, I would like to share my solution to this problem. I developed a utility that indexes your routes, and creates a fiber-like router that can be used in middleware to get the original route.

This solution is a crutch and needs to be improved, but it can be temporarily used until version 3 is released.
I want to say that using this solution will lead to an increase in memory consumption and execution time.

https://github.com/utherbit/fiber_use_route

@mirusky
Copy link
Contributor Author

mirusky commented Aug 8, 2023

@utherbit Interesting approach, kind of encapsulate the fiber.App. I thought something like that but was a loot of trouble implementing it. So I decided to create the manager...

Just one thing, in go we avoid snake case.

By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps.
See: packages names

@sixcolors
Copy link
Member

sixcolors commented Mar 17, 2024

Hi @mirusky,

You can achieve the functionality you're looking for by structuring your middleware so that you are defining permissions directly within the route handler functions. Here's an example:

// in a route handler func
api := app.Group(“/api”)
apiRoutes.TodoRoute(api.Group("/todos"))

// ...

func TodoRoute(route fiber.Router) {
    route.Get("", authz.RequirePermissions([]string{"todos:read"}), controllers.GetTodos)
    route.Get("/:id", authz.RequirePermissions([]string{"todos:read"}), controllers.GetTodo)
    route.Post("", authz.RequirePermissions([]string{"todos:create"}), controllers.CreateTodo)
    route.Patch("/:id", authz.RequirePermissions([]string{"todos:update"}), controllers.UpdateTodo)
    route.Delete("/:id", authz.RequirePermissions([]string{"todos:delete"}), controllers.DeleteTodo)
}

By defining permissions within each route handler, you ensure that permissions are enforced at the granular level of each route. Additionally, you could apply a Role check to routes or even groups of routes, example:

// admin group
adminRoutes := app.Group("/admin")
adminRoutes.Use(authz.RequireRole("admin"))

These approaches allows for a flexible and maintainable way to manage permissions for your routes.

Let me know if you need further assistance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants