-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
All endpoints that would require an interactive login will now return the gRPC status code for `unauthenticated`. This is a signal to the client that they can try to call `login` if they have an interactive context (think UI, CLI). `login` will generate a stream of login commands (currently only supports Browser to signal to the client to open a browser). The interactive login will currently do as many oidc logins as needed to cover all auth scopes. Whats left: I started implementing a tower layer in Bendini (`src/auth.rs`) that checks for `unauthenticated` and in that case tries to perform an interactive login process. However, it turns out not to be as nice as I had hoped. After logging in, we would want to retry the initial request but the request is not cloneable and therefore this seems impossible. See hyperium/tonic#733. This is also the source of the current compilation error that I left in there as reference. We should probably do something like David Pedersen suggests and handle it on a "higher level". Not sure how to make that nice though as the nice thing of what was started here was that it was 100% automatic and invisible to higher layers and putting it higher up makes it dependent on generated types and functions, which tower layers are not since they run after/before protobuf encoding/decoding respectively. Potentially we could use some sort of `Deref` implementation where the client is wrapped in a `Retry` type? Implement the `wait_for_approval` endpoint in avery.
- Loading branch information
1 parent
dbe31c8
commit 445bfed
Showing
7 changed files
with
413 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
use std::{ | ||
fmt::Display, | ||
future::Future, | ||
pin::Pin, | ||
task::{Context, Poll}, | ||
}; | ||
|
||
use firm_types::{ | ||
auth::{authentication_client::AuthenticationClient, interactive_login_command}, | ||
tonic::{self, transport::Channel}, | ||
}; | ||
use futures::{TryFutureExt, TryStreamExt}; | ||
use http::Request; | ||
use tower::Service; | ||
|
||
type BoxedFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct LoginHandler<InnerService> { | ||
inner: InnerService, | ||
auth_client: AuthenticationClient<Channel>, | ||
} | ||
|
||
impl<InnerService> LoginHandler<InnerService> { | ||
pub fn new(inner: InnerService, auth_client: AuthenticationClient<Channel>) -> Self { | ||
LoginHandler { inner, auth_client } | ||
} | ||
} | ||
|
||
impl<InnerService, B> Service<Request<B>> for LoginHandler<InnerService> | ||
where | ||
InnerService: Service<Request<B>, Response = http::Response<tonic::transport::Body>> | ||
+ Clone | ||
+ Send | ||
+ 'static, | ||
InnerService::Future: Future + Unpin + Send, | ||
InnerService::Error: Display, | ||
B: Send + 'static, | ||
{ | ||
type Response = InnerService::Response; | ||
type Error = tonic::Status; | ||
type Future = BoxedFuture<Result<Self::Response, Self::Error>>; | ||
|
||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { | ||
self.inner | ||
.poll_ready(cx) | ||
.map_err(|e| tonic::Status::unavailable(e.to_string())) | ||
} | ||
|
||
fn call(&mut self, req: Request<B>) -> Self::Future { | ||
// This is necessary because tonic internally uses `tower::buffer::Buffer`. | ||
// See https://github.com/tower-rs/tower/issues/547#issuecomment-767629149 | ||
// for details on why this is necessary | ||
let clone = self.inner.clone(); | ||
let mut inner = std::mem::replace(&mut self.inner, clone); | ||
|
||
let mut auth_client = self.auth_client.clone(); | ||
|
||
Box::pin(async move { | ||
inner | ||
.call(req) | ||
.map_err(|e| tonic::Status::aborted(e.to_string())) | ||
.and_then(|r| async move { | ||
match tonic::Status::from_header_map(r.headers()) { | ||
Some(h) if h.code() == tonic::Code::Unauthenticated => { | ||
auth_client | ||
.login(tonic::Request::new(())) | ||
.and_then(|stream| async move { | ||
// Respond to commands in stream | ||
stream | ||
.into_inner() | ||
.try_for_each(|command| async move { | ||
match command.command { | ||
Some( | ||
interactive_login_command::Command::Browser(b), | ||
) => { | ||
println!("TODO: open browser at url {}", b.url); | ||
Ok(()) | ||
} | ||
|
||
None => Ok(()), // 🤔 | ||
} | ||
}) | ||
.and_then(|_| { | ||
// here we would want to retry the earlier | ||
// request but it is not this simple | ||
// unfortunately since the request is not | ||
// Clone: https://github.com/hyperium/tonic/issues/733 | ||
inner | ||
.call(req) | ||
.map_err(|e| tonic::Status::aborted(e.to_string())) | ||
}) | ||
.await | ||
}) | ||
.await | ||
} | ||
_ => Ok(r), | ||
} | ||
}) | ||
.await | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.