ramosbugs / openidconnect-rs

OpenID Connect Library for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Implement PartialEq for the client

Stygmates opened this issue · comments

Is it possible to derive the PartialEq trait so that the following code works? Is there another reason since ramosbugs/oauth2-rs#232 was merged that prevents us from deriving this trait?

use openidconnect::core::CoreClient;

#[derive(PartialEq)]
struct TestStruct {
    client: CoreClient,
}

So far I tried to derive the Client structure of the openidconnect crate and the one in the oauth2 crate alongside the AuthType of the oauth2 crate but now I'm getting an error:

error[E0369]: binary operation `==` cannot be applied to type `Client<EmptyAdditionalClaims, CoreAuthDisplay, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, CoreJsonWebKeyType, CoreJsonWebKeyUse, CoreJsonWebKey, CoreAuthPrompt, StandardErrorResponse<CoreErrorResponseType>, StandardTokenResponse<IdTokenFields<EmptyAdditionalClaims, EmptyExtraTokenFields, CoreGenderClaim, CoreJweContentEncryptionAlgorithm, CoreJwsSigningAlgorithm, CoreJsonWebKeyType>, CoreTokenType>, CoreTokenType, StandardTokenIntrospectionResponse<EmptyExtraTokenFields, CoreTokenType>, CoreRevocableToken, StandardErrorResponse<RevocationErrorResponseType>>`
 --> src/main.rs:5:5
  |
3 | #[derive(PartialEq)]
  |          --------- in this derive macro expansion
4 | struct TestStruct {
5 |     client: CoreClient,
  |     ^^^^^^^^^^^^^^^^^^
  |
  = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0369`.
error: could not compile `test_openidconnect` (bin "test_openidconnect") due to previous error

Re: this issue and #132:

Simply passing through the timing-resistant-secret-traits feature flag won't solve the problem since this crate introduces many new secret types of its own (e.g., Nonce), and we would need to add similar functionality to those types in order for the feature flag to make sense.

I'm curious why the example above expects CoreClient to implement PartialEq, though. I don't even know what it would semantically mean for two clients to be equal, so I don't expect that trait to ever be implemented for Client. The timing-resistant-secret-traits feature specifically adds Hash and PartialEq to the secret types (e.g., CsrfToken) that, for security reasons (to avoid timing attacks), do not implement those traits. The reason it's behind a feature flag is because, to avoid timing attacks, the implementation is much more computationally expensive than typical Hash and PartialEq implementations. Users should opt in rather than assuming they're "normal" implementations and ending up with unexpected performance issues.

This approach works but still doesn't feel ideal, and I'd prefer not to carry it over into this crate. Instead, I think it would be preferable to add a TimingResistant<T: Secret> wrapper type that implements Hash and PartialEq. For storing secrets, users could then do:

#[derive(PartialEq)]
struct Foo {
  nonce: TimingResistant<Nonce>,
}

This makes the behavior more obvious in that code, compared to having a feature flag.

This would require adding a new Secret trait and implementing it for each of the secret types in this crate (and for those imported from oauth2). I'd be happy to merge a PR that implements this solution.

I'm trying to implement an openidconnect example in dioxus: DioxusLabs/dioxus#1500 and I'm trying to use props as stated in DioxusLabs/dioxus#1500 (comment).

As stated in the comment above, I'm running into temporary lifetimes issues when trying to use borrowed props so I'm trying to use regular props instead.

Here's the documentation to props: https://dioxuslabs.com/learn/0.4/reference/component_props and it requires PartialEq to be implemented in order to re-render the component only if the props changed.

Thanks for adding that context; it's an interesting use case! I'm not familiar with Dioxus, but I have a lot of experience with React. In React, passing some sort of client object in as a prop would use reference equality to determine whether to re-render the component. This is one place where Rust's stronger type system makes things like this trickier.

Looking at the Dioxus docs, it feels like a reference prop is the right answer here even if the lifetimes are annoying:

Owned props work well if your props are easy to copy around – like a single number. But what if we need to pass a larger data type, like a String from an App Component to a TitleCard subcomponent?

Even for strings, they're suggesting to use borrowed props. A Client object is much larger than a single string.

An alternative approach is to pass in the values (e.g., ProviderMetadata, ClientId, ClientSecret) needed to instantiate a Client as props rather than the Client itself. If Dioxus has some equivalent of React's useMemo() hook, a component could then instantiate the Client and keep reusing it until one of the relevant props changes. This only works for one level of component rendering though; if that component needs to pass the client down to another component, we run into the same issue.

In any case, I don't think it makes sense for this crate to implement PartialEq on the Client, which would also require the oauth2 crate to do that. This will limit future flexibility since all fields we might want to add to the Client in the future would also have to implement PartialEq. I think this is a problem that needs to be solved outside of this crate.

I ended up following this comment: DioxusLabs/dioxus#1500 (comment) and implemented PartialEq on a structure holding the client and the client_id: https://github.com/DioxusLabs/dioxus/blob/d969d91c84634040a5eaa0294565fd54b37dd29d/examples/openid_connect_demo/src/props/client.rs#L4-L14, I figured it would be better than instanciating a new client each time.

Thanks for your help!