1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
//! Deezer API authentication.
use std::convert::Infallible;
use std::error::Error;
use std::fmt::Display;
use std::net::ToSocketAddrs;
use std::time::Duration;
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::errors::auth::AuthPromptHandlerError;
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);
impl Display for AuthCode
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
self.0.fmt(f)
}
}
/// A Deezer authentication prompt handler.
pub struct AuthPromptHandler
{
/// URL to the Deezer authentication prompt.
pub auth_prompt_url: String,
/// Handle for the running authentication prompt handler.
///
/// Finishes when a single authentication has occurred.
pub handler: JoinHandle<Result<AuthCode, Box<dyn Error + Send + Sync>>>,
}
impl AuthPromptHandler
{
/// Runs a web server that handles a Deezer authentication prompt.
///
/// The argument `address` must be in the same domain as the domain you defined when
/// you created the application with the ID `app_id`.
///
/// # Errors
/// Will return Err if the web server created is unable to bind to `address`.
pub async fn run(
app_id: u32,
address: String,
port: u16,
uri_scheme: String,
) -> Result<Self, AuthPromptHandlerError>
{
let (done_tx, mut done_rx) = mpsc::channel::<AuthCode>(1);
let addr = format!("{}:{}", address, port)
.to_socket_addrs()
.map_err(|_| AuthPromptHandlerError::InvalidAddress)?
.next()
.map_or_else(|| Err(AuthPromptHandlerError::InvalidAddress), Ok)?;
let make_service = make_service_fn(move |_| {
let done_tx_clone = done_tx.clone();
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 => {
result.map(|_| None)
},
auth_code = async {
done_rx.recv().await
} => Ok(auth_code)
}?;
Ok(opt_auth_code.map_or_else(|| Err("No auth code was received"), Ok)?)
});
Ok(Self {
auth_prompt_url: format!(
"{}?app_id={}&redirect_uri={}://{}:{}&perms=basic_access",
AUTH_URL, app_id, uri_scheme, address, port
),
handler: handle,
})
}
}
#[derive(Debug, Deserialize)]
struct AuthCodeQuery
{
error_reason: Option<String>,
code: Option<AuthCode>,
}
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())
}
|