dermesser / yup-oauth2

An oauth2 client implementation providing the Device, Installed, Service Account, and several more flows.
https://docs.rs/yup-oauth2/
Apache License 2.0
223 stars 116 forks source link

Use actix-web instead of hyper #135

Open vitalije opened 4 years ago

vitalije commented 4 years ago

I needed to send authenticated google api requests from my actix-web server using my service account credentials. I've found your great project, but it uses hyper by default. I didn't want to add hyper to my dependences, so I've changed your code to use actix web client. To make my task easer I stripped all other authentication flows (installed and device). The result is published as a service-authenticator crate.

Source code can be found here

Hope this will help someone.

Skyggedans commented 3 years ago

Could you provide some examples on using your authenticator inside actor context, please? I need to send a message via FCM inside Actor's StreamHandler after getting some message from another actor, but I stuck on yup-oauth2 ServiceAccountAuthenticator flow, my application panics with the error, saying that I'm trying to make Tokio calls outside the Tokio runtime. I didn't try your crate yet, but since you use tokio at least in a couple of places, I expect the same behaviour.

vitalije commented 3 years ago

I am not sure that the following example is what you are looking for. But this is something I use in my actix-web server:

use service_authenticator::authenticator::{Authenticator, AuthenticatorBuilder as AB};
use service_authenticator::parse_service_key;
static SERVICE_CREDENTIALS:&[u8] = include_bytes!("my-credentials.json");
static GMAIL_SCOPES: &[&str] = &[
  "https://www.googleapis.com/auth/gmail.labels",
  "https://www.googleapis.com/auth/gmail.send",
  "https://www.googleapis.com/auth/gmail.readonly",
  "https://www.googleapis.com/auth/gmail.compose",
  "https://www.googleapis.com/auth/gmail.insert",
  "https://www.googleapis.com/auth/gmail.modify",
  "https://www.googleapis.com/auth/gmail.metadata",
  "https://www.googleapis.com/auth/gmail.settings.basic",
  "https://www.googleapis.com/auth/gmail.settings.sharing",
  "https://mail.google.com/",
  "https://www.googleapis.com/auth/spreadsheets",
]; // find out which scopes you actually need

async fn create_authenticator() -> std::io::Result<Authenticator> {
  let k = parse_service_key(SERVICE_CREDENTIALS)
    .expect("bad gmail credentials");
  AB::with_service_key(k, "my-gmail-account@example.com")
    .build()
    .await
}
async fn do_get_range_from_sheet(
  auth: &Authenticator,
  a: &str,
  b: &str,
  sheet_id: &str,
  ) -> Result<Vec<u8>, Error> {
  let url = format!(
    "https://sheets.googleapis.com/v4/spreadsheets/{}/values/{}%3A{}?dateTimeRenderOption=FORMATTED_STRING&majorDimension=ROWS&valueRenderOption=FORMATTED_VALUE",
    sheet_id,
    a,
    b,
  );
  if let Ok(h) = auth.header(GMAIL_SCOPES).await {
    let res = auth
      .client
      .get(&url)
      .header("Authorization", h.as_str())
      .header("Accept", "application/json")
      .send()
      .await;
     if let Ok(mut r) = res {
      let xr = r.body().await;
      return match xr {
        Ok(x) => Ok(x.to_vec()),
        Err(e) => {println!("Error:{:?}", e); Ok(Vec::new())}
      }
    } else {
      println!("error sending request");
      Ok(Vec::new())
    }
  } else {
    println!("can't get authorization header");
    Ok(Vec::new())
  }
}
pub async fn get_sheet(
  auth: web::Data<Authenticator>,
  pool: web::Data<DbPool>,
  req: HttpRequest,
  ) -> HttpResponse {
  let a: &str = req.match_info().query("a");
  let b: &str = req.match_info().query("b");
  let sheet_id: &str = req.match_info().query("sheet");
  match do_get_range_from_sheet(&auth, a, b, sheet_id).await
    { Ok(s) => HttpResponse::Ok()
                .header("Content-Type", "application/json")
                .body(s)
    , Err(e) => {
        println!("Error: {:?}", e);
        HttpResponse::InternalServerError().body(format!("{:?}", e))
      }
    }
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
  HttpServer::new(move || {
    App::new()
      .data_factory(create_authenticator)
      .route("/api/sheet/{sheet}/{a}/{b}", web::get().to(get_sheet))
  })
  .bind(&bind_address)?
  .run()
  .await
}

I made this example just by copying and pasting pieces from my source code. I haven't tried to compile it. In my source this functions are in separate modules, and also in this example I've showed only "use" lines for service-authenticator crate, but you'll certainly need to import other crates. I hope you can find your way using this and perhaps some basic actix-web application.

Skyggedans commented 3 years ago

Thanks for the response. I've tried your lib already, and the tokio error has gone, I think it's because you use tokio 0.2, while yup-outh2 uses 1.0, and actix uses 0.2 too. But now I have another bug. Authenticator fails with timeout error of the underlying client: thread 'main' panicked at 'Failed to get authorization token: HttpError(Connect(Timeout))' I copied the rqbody for token_uri call, pasted it into Postman, and got next: { "error": "unauthorized_client", "error_description": "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested." } The bodies for that call from your authenticator and yup-oauth2 look pretty the same, but the last one successfully retrieves the token for me, being run as a clean example (without actix).

vitalije commented 3 years ago

Sorry I couldn't help you more. I remember that I've also had some issues with authorization with google. I've contacted their support team several times. You need to register you client with google and receive from them credentials.json for your application. But you also need to authorize your application as a google user in order to allow your application to use google apis on your behalf. I don't remember the details but try to ask google support team for solving authorization problems.

Skyggedans commented 3 years ago

Thanks for your attention, Виталије! I'm trying to find a solution to send messages via FCM to the Flutter application on Android from Rust program (some kind of a very immature IoT hub, https://github.com/Skyggedans/ajax_alarm) based on Actix. I've already registered my app in a Firebase, got credentials.json from FB console and managed to do this with Google's OAuth2 Playground and Postman. I've already managed to retrieve auth token with yup-oauth2, by using their https://github.com/dermesser/yup-oauth2/tree/master/examples/service_account example as a base for my testing snippet for my credentials.json. The only thing I was unable to accomplish, is that I can't integrate it into my real app due to those Tokio version incompatibility errors. I switched from yup-oauth2 to your crate and tried to use the same JSON file, and encoutered that timeout error. I was thinking, since the only thing you changed is an HTTP client, the logic wasn't affected and I could replace it transparently. Ok, I will debug it today, maybe I can get it to work.

dermesser commented 3 years ago

Note that yup-oauth2 v4 has the old pre-1.0 tokio version, in case that helps. It will be supported for a bit.

Skyggedans commented 3 years ago

Note that yup-oauth2 v4 has the old pre-1.0 tokio version, in case that helps. It will be supported for a bit.

Thanks. Were there any reasons to switch to Tokio 1.0 in 5.x? Because I tried to downgrade to Tokio 0.2.22 in 5.0.1 and it compiles ok (not counting a single fix of tokio's time::sleep back to time::delay_for). I suppose, it's because the newest hyper, which depends on 1.0? I can try to switch to actix-web, as @vitalije already did, but I suspect then I'll get exactly the same thing as service-authenticator. There's one difference between yup-oauth2 and service-authenticator, is that @vitalije explicitly sets subject, and I think this can be an issue that makes impossible to get the auth token successfully.

dermesser commented 3 years ago

It's the other way around: Switching to tokio 1.0 was the reason for releasing v5.x. Because they are incompatible, and many other folks are probably upgrading their software to tokio 1.0.

Thus it's recommended that you use yup-oauth2 4.x for tokio 0.2 and yup-oauth2 5.x for tokio 1.0. There is no difference in functionality - the big version bump is purely about tokio (a minor release is not possible, as switching tokio/hyper also changes the public API).

Skyggedans commented 3 years ago

Thanks, I'll check 4.x.

extrawurst commented 3 years ago

Yeah in fact yup was one of the last crates we waited for to receive tokio 1.0 support! thanks for your work @dermesser 👍