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
use std::{
  sync::Arc,
  thread::{self, JoinHandle},
};

use dropbox_sdk::{
  default_client::NoauthDefaultClient,
  oauth2::{Authorization, AuthorizeUrlBuilder, Oauth2Type, PkceCode},
};
use log::error;
use tiny_http::{Header, Response, Server};
use url::Url;

use crate::block_store::{dropbox::APP_KEY, StoreError, StoreResult};

const REDIRECT_URL: &str = "http://127.0.0.1:9898";

const AUTHCODE_RESPONSE_BODY: &str = r#"
<!DOCTYPE html>
<html>
<head>
<title>t-rust-less</title>
</head>
<body">
<p style="text-align: center;">&nbsp;</p>
<p style="text-align: center;">T-Rust-Less is now authenticated to Dropbox!</p>
<p style="text-align: center;">You may now close this window...</p>
</body>
</html>
"#;

pub struct ServerHandle {
  server: Arc<Server>,
  join_handle: Option<JoinHandle<Result<String, String>>>,
}

impl ServerHandle {
  fn wait_for_auth_code(&mut self) -> StoreResult<String> {
    let join_handle = match self.join_handle.take() {
      Some(join_handle) => join_handle,
      None => return Err(StoreError::IO("Already waiting".to_string())),
    };
    match join_handle.join() {
      Ok(Ok(authcode_url)) => Ok(
        Url::parse(&authcode_url)?
          .query_pairs()
          .find_map(|(key, value)| if key == "code" { Some(value.to_string()) } else { None })
          .ok_or_else(|| StoreError::IO("auth url does not contain code".to_string()))?,
      ),
      Ok(Err(err)) => {
        error!("Failed receiving dropbox authcode {}", err);
        Err(StoreError::IO(err))
      }
      Err(err) => {
        error!("Failed receiving dropbox authcode {:?}", err);
        Err(StoreError::IO(format!("{:?}", err)))
      }
    }
  }
}

impl Drop for ServerHandle {
  fn drop(&mut self) {
    self.server.unblock();
  }
}

pub struct DropboxInitializer {
  name: String,
  oauth2_flow: Oauth2Type,
  pub auth_url: Url,
  server_handle: ServerHandle,
}

impl DropboxInitializer {
  pub fn wait_for_authentication(mut self) -> StoreResult<String> {
    let auth_code = self.server_handle.wait_for_auth_code()?;

    let mut authorization = Authorization::from_auth_code(
      APP_KEY.to_string(),
      self.oauth2_flow.clone(),
      auth_code,
      Some(REDIRECT_URL.to_string()),
    );
    authorization.obtain_access_token(NoauthDefaultClient::default())?;
    let token = authorization
      .save()
      .ok_or_else(|| StoreError::IO("Failed to obtain dropbox token".to_string()))?;

    Ok(format!("dropbox://{}@{}", token, self.name))
  }
}

pub fn initialize_store(name: &str) -> StoreResult<DropboxInitializer> {
  let oauth2_flow = Oauth2Type::PKCE(PkceCode::new());
  let auth_url = AuthorizeUrlBuilder::new(APP_KEY, &oauth2_flow)
    .redirect_uri(REDIRECT_URL)
    .build();
  let server_handle = start_authcode_server()?;

  Ok(DropboxInitializer {
    name: name.to_string(),
    oauth2_flow,
    auth_url,
    server_handle,
  })
}

pub fn start_authcode_server() -> StoreResult<ServerHandle> {
  let server = Arc::new(Server::http("127.0.0.1:9898").map_err(|e| StoreError::IO(format!("{}", e)))?);
  let server_cloned = server.clone();

  let join_handle = thread::spawn(move || {
    let request = server_cloned.recv().map_err(|e| format!("{}", e))?;
    let url = format!("{}{}", REDIRECT_URL, request.url());
    request
      .respond(
        Response::from_data(AUTHCODE_RESPONSE_BODY)
          .with_header(Header::from_bytes(&b"Content-Type"[..], &b"text/html; charset=UTF-8"[..]).unwrap()),
      )
      .ok();

    Ok(url)
  });

  Ok(ServerHandle {
    server,
    join_handle: Some(join_handle),
  })
}