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

Form Request Guards #2902

Open
2 tasks done
C0D3-M4513R opened this issue Jan 16, 2025 · 1 comment
Open
2 tasks done

Form Request Guards #2902

C0D3-M4513R opened this issue Jan 16, 2025 · 1 comment
Labels
request Request for new functionality

Comments

@C0D3-M4513R
Copy link

C0D3-M4513R commented Jan 16, 2025

What's missing?

I want to implement an OAuth Client in Rocket.
For that I have decided to keep some stuff, like currently authenticated users and csrf tokens in rocket's managed state.

e.g. Twitch and Discord pass a csrf state back in the state query parameter.
I now want to add a Request Guard on that state query parameter, which requires me to write a FromForm (or FromFormField) implementation.
The issue with writing such an implementation is, that I cannot get the csrf tokens out of rocket's managed state.

Also in such a scenario it is very unclear, how an error will be displayed to the end user.
With FromRequest it is pretty clear to me what will happen (there is good documentation and the method signatures makes it clear what will happen).
With FromForm I am still stuck thinking: If I return an error, how will this affect the actual "main responder method body" and how (if at all) are errors from FromForm displayed in the response?

e.g.

struct CSRF(String);
impl FromFormField for CSRF { /*.... some magic impl here .... */}

//When would this stop running?
#[rocket::get("/test?<state>")]
async fn test(state: CSRF) -> &'static str { "Hello World" }

Ideal Solution

Writing query parameter/"form" request guards would be possible.
query parameter request guards would allow the conversion and validation of one query parameter, whilst referencing rocket managed state (similarly to FromRequest).
When the validation fails, it should be clear what happens after from the method signature and documentation (similarly to FromRequest)

Why can't this be implemented outside of Rocket?

Implementing a feature like this outside of Rocket (in the way I would want it, without any drawbacks) would likely require redoing how requests are dispatched to request handlers.
I imagine, that that part of rocket is non-extendable.

Are there workarounds usable today?

Yes. I can think of 2 methods:

One could just implement FromRequest for a custom CSRF struct, which accesses a compile-time specified (e.g. using type constant arguments) query parameter using Request::query_value and gets all valid csrf's from managed rocket state.

Then a validation can be performed without issue, and if needed a request handler can be blocked from
running.

This method relies on Request::query_value, which is noted as: "Warning
This method exists only to be used by manual routing and should never be used in a regular Rocket application."

Therefore I think that this approach is discouraged by Rocket

That being said, I for now went with this approach, since it's the easiest and closest to what I want:

One could make a custom macro, which wraps each applicable request handler function to add csrf validation.

Downside: Writing such a macro (depending on versatility) might be really hard.
Especially when going into the territory of proc_macros, which change the function return type and transparently wrap it.

e.g.

//before application of custom::csrf_wrapper
#[rocket::get("/test?<state>")]
#[custom::csrf_wrapper("state")] //duplicated specifier
async fn test(state: &str) -> &'static str { "Hello World" }

//after application of custom::csrf_wrapper
#[rocket::get("/test?<state>")]
#[custom::csrf_wrapper("state")] //duplicated specifier
async fn test<'r>(state: &'r str) -> impl rocket::response::Responder<'r, 'static> {
  #[derive(rocket::response::Responder)] 
  enum __impl_Error {
    OriginalFunction(&'static str),
    CSRFError(rocket::response::content::RawHTML<&'static str>),
  }
  if let Err(err) = some_verify_csrf_function(state).await {
    return __impl_Error::CSRFError(err);
  }
  //Original Function
  async fn test(state: &str) -> &'static str { "Hello World" }
  
  __impl_Error::OriginalFunction(test(state).await)  
}

Alternative Solutions

No response

Additional Context

I have not been using rocket for a long time. I have started using rocket 1-2 days ago. I also have no clue how this library/framework works internally.

System Checks

  • I do not believe that this feature can or should be implemented outside of Rocket.
  • I was unable to find a previous request for this feature.
@C0D3-M4513R C0D3-M4513R added the request Request for new functionality label Jan 16, 2025
@the10thWiz
Copy link
Collaborator

Another workaround usable today, is to create two guards - a CSRFToken and a CSRFLookup guard. The CSRFToken implements FromFormField (and others as needed), while CSRFLookup holds a reference to the CSRF managed state, and provides a lookup method to convert a CSRFToken to an AuthenticatedCSRFToken.

This would cause some code duplication, and isn't ideal.

I think the long term solution is to simply allow FromParameter, FromSegments, FromFrom, etc to access the &Request, making the type of implementation desired here quite trivial.

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

No branches or pull requests

2 participants