TL;DR: I need an example from someone who knows the Rust/Swagger generated server code on how to write a middleware layer for authentication.
I've been working on a REST API built from an OpenApi spec (Swagger-file) with
Rust, and I want to implement some authentication with a middleware layer.
I have spec file that lists a number of services, that are all secured with an
API key. Whenever a service is called, I would like to peek at the HTTP request,
before running the actual service.
I want to validate the API key, and possibly do some back-channel server calls.
If the API key checks out, I want to run the actual service code, but if it doesn't,
I want to return a custom HTTP response.
The Rust generator uses a Hyper server, and generates some middleware in the form
of Tower services. I thought it would be a picnic to replace the "AllowAll"
middleware with one of my own making, but so far, I have had no success.
I think I understand how Tower services are supposed to work, but the ones generated
by the Swagger generator are a little more complex, to say the least, and they take
the form of a two step process, where one middleware generates another service and
wraps that, which then in turn wraps the preciously added middleware.
Long story short, I need some help from more experienced Rustaceans
For the sake of simplicity, let's say I have a spec, middleware.yaml
like this:
openapi: 3.0.3
info:
title: Middleware Test
version: 0.0.1
paths:
/test:
get:
operationId: test
responses:
"200":
description: Ok
security:
- api_key: []
components:
securitySchemes:
api_key:
type: apiKey
name: api_key
in: header
I generate a server project using the openapi-generator-cli
tool:
npx --yes @openapitools/openapi-generator-cli generate \
--generate-alias-as-model \
--api-package middleware \
--package-name middleware \
-g rust-server \
-i middleware.yaml
This generates a library crate that I can then use in my own code. For the sake
of simplicity, we can look at the example server, generated by the tool
(in examples/server/server.rs
). The interesting part is the function that
creates the Hyper server:
pub async fn create(addr: &str) {
let addr = addr.parse().expect("Failed to parse bind address");
let server = Server::new();
let service = middleware::server::MakeService::new(server);
let service = swagger::auth::MakeAllowAllAuthenticator::new(service, "cosmo");
let service = middleware::server::context::MakeAddContext::<_, EmptyContext>::new(service);
hyper::server::Server::bind(&addr).serve(service).await.unwrap()
}
It creates an implementation of the API (Server::new()
), and wraps it in three services.
My problem is, that the combination of two-tiered wrappers (i.e. MakeAllowAllAuthenticator
is a wrapper for an internal AllowAllAuthenticator
which in turns wraps the previous
MakeService
that wraps a Service
) and the Rust-Swagger concept of the context which is
not a plain struct but a set of trait bounds that hides a seemingly dynamic implementation.
This creates a very complex set of boundary requirements.
So, I'm hoping I'm not the only one using the Rust/Swagger generator, and that someone can
point me at an example of a middleware implementation, in the expected two service format
(MakeFoo
/Foo
) that inspects the raw HTTP body, and possibly returns an early result,
instead of proceeding with the chain.
Like I said, the short term purpose is to plug in my authentication code, but in the long term
I would like to clean up my code by moving some things I do in every service method into the
context that is built by the create
method.
I also posted this question to SO, and I apologize for reposting it here.