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

New hook after route has been found but before it's been evaluated #946

Open
BioTimHaley opened this issue Mar 7, 2024 · 1 comment
Open
Labels
difficulty: intermediate Enterprising community members could help effort: low < 1 day of work help wanted Solution is well-specified enough that any community member could fix theme: middleware Only execute endpoint specific code

Comments

@BioTimHaley
Copy link

Background

My R application is a microservice that calls a larger service to authenticate requests. I need to do this for every request so that only authorized users are able to use my service. I currently have it implemented something like this:

#* @filter auth
auth <- function (req, res) {
    is_valid = expensive_token_checking(...)
    if (!is_valid) {
        res$status <- 401
        return(list(error = "Invalid token"))
    } else {
        plumber::forward()
    }
}

#* @get /foo
function (req, res) {
    ...
}

#* @get /bar
function (req, res) {
    ...
}

This works as expected, however, bots are attempting to scan my service looking for common vulnerabilities which means I have spikes of traffic looking for generic routes like /.htaccess. The issue here is that for requests which do not have a matching route, I don't want to waste my server's resources. I'd like to greedily return a 404 without needing to perform the expensive action of checking the token.

As currently implemented, these requests make it to my middleware even though the are invalid. Even if they had valid tokens, they would make it through my middleware and then receive a 404 from plumber.

Attempted solution

I tried to solve this with hooks. I tried to move my middleware into a hook that way I could leverage plumber's validation of the request while also being able to define reusable code that rejects unauthorized requests, but it appears that neither preroute or postroute hooks work as I need them to.

The preroute hook seems to evaluate before the request has found the endpoint that can handle it (which makes sense), and postroute appears to run after the code inside my route has executed. So neither really work for me. I need some way to execute code after plumber has validated that the verb + route combo is valid and before my route has actually executed.

Proposed solution

I might be misunderstanding what postroute is supposed to do. I would have expected it to intervene in the exact moment I needed it to and then I would use postserialize to execute code after my route, but ideally there would be a new kind of hook introduce that allowed me to hook into the point in the execution I need to leverage. Perhaps postvalidation or postroutefound? Those aren't great names, but just off the top of my head.

If I am misunderstanding hooks and there is a way to achieve exactly what I want to, I apologize. The conclusions I wrote above are from adding logging into each possible hook and a test route to check the order they execute.

@schloerke schloerke added the theme: middleware Only execute endpoint specific code label Mar 8, 2024
@schloerke
Copy link
Collaborator

So this is a solution works with today's code given the matching route. (I'd like to have auth be able to reject the route being found to allow another route to be attempted, but this is not possible in today's code.)


Adjusted the code above so that it can run. Added a preexec hook on each route. This gives you access to route specific behavior only when the route is going to be executed.

auth <- function(req, res) {
  message("Checking token for ", req$PATH_INFO)
  # is_valid <- expensive_token_checking(...)
  is_valid <- TRUE
  if (!is_valid) {
    res$status <- 401
    return(list(error = "Invalid token"))
  } else {
    plumber::forward()
  }
}

#* @get /foo
function(req, res) {
  "foo!"
}

#* @get /bar
function(req, res) {
  "bar!"
}


#' @plumber
function(pr) {
  # Add a preexec hook to all routes that should use `auth`
  lapply(pr$routes, function(route) {
    route$registerHook("preexec", auth)
  })

  pr
}

Looking at the logs in the server after I go to /foo, I see

Checking token for /foo

After visiting /.htaccess, there are no logs showing that auth was used.


It's not a crazy idea to add a new

  • #' @hook preexec <FN>
  • #' @hook postexect <FN>
  • #' @hook aroundexec <FN>
    handlers within
    plumbBlock <- function(lineNum, file, envir = parent.frame()){
    .
hooks = list(
  preexec = list(),
  postexec = list(),
  aroundexec = list()
)

The hooks could then be registered after the endpoint is created near

.

# untested
Map(
  names(hooks), 
  hooks,
  f = function(hook_name, hook_arr) {
    lapply(hook_arr, function(hook) {
      ep$registerHook(hook_name, hook)
    })
  }
)

@schloerke schloerke added difficulty: intermediate Enterprising community members could help effort: low < 1 day of work help wanted Solution is well-specified enough that any community member could fix labels Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
difficulty: intermediate Enterprising community members could help effort: low < 1 day of work help wanted Solution is well-specified enough that any community member could fix theme: middleware Only execute endpoint specific code
Projects
None yet
Development

No branches or pull requests

2 participants