diff options
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/auth.rs (renamed from src/auth/mod.rs) | 92 | ||||
-rw-r--r-- | src/auth/service.rs | 40 | ||||
-rw-r--r-- | src/errors/auth.rs | 4 |
4 files changed, 76 insertions, 64 deletions
@@ -4,12 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] -actix-web = { version = "4.1.0", default-features = false, features = ["macros"] } tokio = { version = "1.21.0", features = ["macros", "rt-multi-thread"] } serde = { version = "1.0.144", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0.33" -hyper = { version = "0.14.20", features = ["client", "http2", "tcp"] } +hyper = { version = "0.14.20", features = ["client", "http2", "tcp", "server", "http1"] } +serde_urlencoded = "0.7.1" [dev_dependencies] config = "0.13.2" diff --git a/src/auth/mod.rs b/src/auth.rs index ce550e1..4af4997 100644 --- a/src/auth/mod.rs +++ b/src/auth.rs @@ -1,22 +1,32 @@ //! Deezer API authentication. +use std::convert::Infallible; use std::error::Error; use std::fmt::Display; +use std::net::ToSocketAddrs; use std::time::Duration; -use actix_web::web::Data; -use actix_web::{App, HttpServer}; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Request, Response, Server}; use serde::Deserialize; use tokio::sync::mpsc; use tokio::task::JoinHandle; use tokio::{select, spawn}; -use crate::auth::service::retrieve_auth_code; use crate::errors::auth::AuthPromptHandlerError; -mod service; - const AUTH_URL: &str = "https://connect.deezer.com/oauth/auth.php"; +/// A Deezer access token. +#[derive(Debug, Clone)] +pub struct AccessToken +{ + /// The access token. + pub access_token: String, + + /// The duration until the access token expires. + pub expires: Duration, +} + /// A Deezer authentication code. #[derive(Debug, Deserialize, Clone)] pub struct AuthCode(String); @@ -59,21 +69,26 @@ impl AuthPromptHandler { let (done_tx, mut done_rx) = mpsc::channel::<AuthCode>(1); - let done_tx_data = Data::new(done_tx); + let addr = format!("{}:{}", address, port) + .to_socket_addrs() + .map_err(|_| AuthPromptHandlerError::InvalidAddress)? + .next() + .map_or_else(|| Err(AuthPromptHandlerError::InvalidAddress), Ok)?; - let server = HttpServer::new(move || { - App::new() - .app_data(done_tx_data.clone()) - .service(retrieve_auth_code) - }) - .bind((address.clone(), port)) - .map_err(|_| AuthPromptHandlerError::BindAddressFailed)?; + let make_service = make_service_fn(move |_| { + let done_tx_clone = done_tx.clone(); - let server_future = server.run(); + let service = + service_fn(move |req| handle_auth_code(done_tx_clone.clone(), req)); + + async move { Ok::<_, Infallible>(service) } + }); + + let server = Server::bind(&addr).serve(make_service); let handle = spawn(async move { let opt_auth_code = select! { - result = server_future => { + result = server => { result.map(|_| None) }, auth_code = async { @@ -94,13 +109,46 @@ impl AuthPromptHandler } } -/// A Deezer access token. -#[derive(Debug, Clone)] -pub struct AccessToken +#[derive(Debug, Deserialize)] +struct AuthCodeQuery { - /// The access token. - pub access_token: String, + error_reason: Option<String>, + code: Option<AuthCode>, +} - /// The duration until the access token expires. - pub expires: Duration, +async fn handle_auth_code( + done_tx: mpsc::Sender<AuthCode>, + request: Request<Body>, +) -> Result<Response<Body>, String> +{ + let query = serde_urlencoded::from_str::<AuthCodeQuery>( + request.uri().query().map_or_else(|| "", |query| query), + ) + .map_err(|err| format!("Invalid query. {}", err))?; + + if let Some(error_reason) = &query.error_reason { + return Ok(Response::builder().status(401).body(Body::from(format!( + "Error: No authentication code was retrieved. Reason: {}\n\nYou can close this tab", + error_reason + ))).unwrap()); + } + + let auth_code = match &query.code { + Some(auth_code) => auth_code, + None => { + return Ok(Response::builder() + .status(400) + .body(Body::from("Error: No authentication code was retrieved.")) + .unwrap()); + } + }; + + done_tx.send(auth_code.clone()).await.unwrap(); + + Ok(Response::builder() + .status(200) + .body(Body::from( + "Authentication code was successfully retrieved.\n\nYou can close this tab", + )) + .unwrap()) } diff --git a/src/auth/service.rs b/src/auth/service.rs deleted file mode 100644 index b9b44d4..0000000 --- a/src/auth/service.rs +++ /dev/null @@ -1,40 +0,0 @@ -use actix_web::web::{Data, Query}; -use actix_web::{get, HttpResponse}; -use serde::Deserialize; -use tokio::sync::mpsc; - -use crate::auth::AuthCode; - -#[derive(Debug, Deserialize)] -struct AuthCodeQuery -{ - error_reason: Option<String>, - code: Option<AuthCode>, -} - -#[get("/")] -async fn retrieve_auth_code( - query: Query<AuthCodeQuery>, - done_tx: Data<mpsc::Sender<AuthCode>>, -) -> HttpResponse -{ - if let Some(error_reason) = &query.error_reason { - return HttpResponse::Unauthorized().body(format!( - "Error: No authentication code was retrieved. Reason: {}\n\nYou can close this tab", - error_reason - )); - } - - let auth_code = match &query.code { - Some(auth_code) => auth_code, - None => { - return HttpResponse::BadRequest() - .body("Error: No authentication code was retrieved"); - } - }; - - done_tx.send(auth_code.clone()).await.unwrap(); - - HttpResponse::Ok() - .body("Authentication code was successfully retrieved.\n\nYou can close this tab") -} diff --git a/src/errors/auth.rs b/src/errors/auth.rs index 656673e..a165e05 100644 --- a/src/errors/auth.rs +++ b/src/errors/auth.rs @@ -7,4 +7,8 @@ pub enum AuthPromptHandlerError /// HTTP server failed to bind to a address. #[error("HTTP server failed to bind to address")] BindAddressFailed, + + /// Invalid address. + #[error("Invalid address")] + InvalidAddress, } |