paperclip icon indicating copy to clipboard operation
paperclip copied to clipboard

Support basic auth if specified in security definitions

Open wafflespeanut opened this issue 6 years ago • 4 comments

Currently, we support adding root certs and enabling client verification, but we also need basic auth. We should support that in the generated client if the field is specified and add an option to CLI regardless.

Note that basic auth can be specified globally or locally to some particular operation.

wafflespeanut avatar Jul 08 '19 08:07 wafflespeanut

I would like to work on this.

letmutx avatar Oct 02 '19 08:10 letmutx

Cool!

wafflespeanut avatar Oct 02 '19 08:10 wafflespeanut

So, this is basically what I had in mind.

The two traits of interest are ApiClient and Sendable.

trait ApiClient {
    //
}
trait Sendable {

    type Output: DeserializeOwned + Send + 'static;
    
    // other thingies ...
    
    fn send(&self, client: &dyn ApiClient) -> Box<...> {
        //
    }
}

A number of things here should be type/generic parameters, but they're coupled to reqwest::async::* as of now (which is fine, because reqwest doesn't have a competition).

Sendable is implemented for all "fulfilled" builder structs i.e., builders which have the necessary parameters for making an API call. So, each impl represents an operation.

In OpenAPI, auth can be imposed on all operations or one particular operation. In order to add type-level constraint for auth, we should extend ApiClient for security stuff.

trait ApiClient<Auth> {
    //
}

struct NoAuth;

By default, reqwest::async::Client (which currently impls ApiClient) will now impl ApiClient<NoAuth>. We'll have an extension trait for transforming the default client to other kinds of clients.

struct AuthBasic;
struct AuthApiKey;

trait ClientExt {
    type BasicAuthClient: ApiClient<AuthBasic>;
    type ApiKeyClient: ApiClient<AuthApiKey>;
    // Other things (like OAuth) will follow later.
    
    fn with_basic_auth(&self, user: &str, pass: &str) -> Self::BasicAuthClient;
    
    fn with_api_key(&self, key: &str) -> Self::ApiKeyClient;
}

Then, we'll have wrappers for the actual client which store auth data.

struct ReqwestBasicAuth {
    user: String,
    pass: String,
    client: reqwest::async::Client;
}

impl ApiClient<AuthBasic> for ReqwestBasicAuth {
    // `make_request` will call `make_request` on the inner client
    // and will set the basic auth on the emitted request object.
}

We're binding Auth as a generic to Sendable, because we can't limit Sendable to the base ApiClient (no AsRef<BaseClient>), and that's because the different kinds of clients store the auth data. But, for operations, auth data is actually needed by the RequestBuilder (or Request) structs emitted by those clients (and not the clients themselves) as shown above.

Now, the trait impl for some operation with basic auth will look like:

impl Sendable<AuthBasic> for SomeBuilder<FooExists> {
    // thingies ...
}

... whereas for non-auth operations, we'll have:

impl<A> Sendable<A> for SomeBuilder<FooExists> {
    // thingies ...
}

wafflespeanut avatar Oct 06 '19 17:10 wafflespeanut

... reqwest doesn't have a competition.

I was wrong. Surf is here and it looks really promising with async/await. I guess it's time to rethink ApiClient and Sendable.

wafflespeanut avatar Oct 06 '19 20:10 wafflespeanut