summaryrefslogtreecommitdiff
path: root/src/auth/mod.rs
blob: ce550e174409bfd500c6964811dd755ff429a69e (plain)
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
//! Deezer API authentication.
use std::error::Error;
use std::fmt::Display;
use std::time::Duration;

use actix_web::web::Data;
use actix_web::{App, HttpServer};
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 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 done_tx_data = Data::new(done_tx);

        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 server_future = server.run();

        let handle = spawn(async move {
            let opt_auth_code = select! {
                result = server_future => {
                    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,
        })
    }
}

/// 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,
}