cesium_oauth/apps/
mod.rs

1use async_trait::async_trait;
2use log::{debug, trace};
3use serde::*;
4
5#[cfg(feature = "app-file-cache")]
6pub use file_cached::FileCachedAppProvider;
7
8use crate::error::Error;
9
10#[cfg(feature = "app-file-cache")]
11mod file_cached;
12
13/// Global information about an app, can be shared across multiple instances.
14#[derive(Serialize, Deserialize, Clone, Debug)]
15pub struct AppInfo {
16    /// Display name of the app
17    pub name: String,
18    /// Where the user should be redirected after authorization
19    pub redirect_uri: String,
20}
21
22/// A specific registered app, registered at one instance.
23#[derive(Serialize, Deserialize, Clone, Debug)]
24pub struct RegisteredApp {
25    /// Display name of the app
26    pub name: String,
27    /// Client ID key, to be used for obtaining OAuth tokens
28    pub client_id: String,
29    /// Client secret key, to be used for obtaining OAuth tokens
30    pub client_secret: String,
31}
32
33/// Trait for application providers. An application provider is expected to be
34/// able to register an app at an instance, and then store and re-use it. Once
35/// an application has been registered at an instance, any further requests for
36/// that instance should result in the same [RegisteredApp].
37///
38/// For an example implementation, look at [FileCachedAppProvider].
39#[async_trait]
40pub trait AppProvider {
41    /// If we don't yet have an app registered at that instance, register it and return
42    /// the [RegisteredApp]. Otherwise, return the already existing [RegisteredApp].
43    async fn get_app_for(&mut self, instance_domain: &str) -> Result<RegisteredApp, Error>;
44}
45
46impl AppInfo {
47    /// Simple constructor for creating application info.
48    pub fn new(name: &str, redirect_uri: &str) -> Self {
49        Self {
50            name: name.to_owned(),
51            redirect_uri: redirect_uri.to_owned(),
52        }
53    }
54}
55
56/// Generic function for registering a new app to an instance. Uses [reqwest] so
57/// should be compatible across platforms.
58pub async fn register_new_app(
59    instance_domain: &str,
60    app: &AppInfo,
61) -> Result<RegisteredApp, Error> {
62    let url = format!("https://{instance_domain}/api/v1/apps");
63    let params = [
64        ("client_name", app.name.as_str()),
65        ("redirect_uris", app.redirect_uri.as_str()),
66        ("scopes", "read:accounts"),
67    ];
68
69    debug!("Registering new app '{}' at `{instance_domain}`", app.name);
70    trace!("Registration url is {url}");
71
72    let client = reqwest::Client::new();
73    let response = client
74        .post(&url)
75        .form(&params)
76        .send()
77        .await
78        .map_err(|_| Error::AppRegistrationError("Failed to connect to server"))?;
79
80    trace!("Response status {}", response.status());
81    if response.status() != 200 {
82        return Err(Error::TokenObtainError("Authorization failure"));
83    }
84
85    let registered_app: RegisteredApp = response
86        .json()
87        .await
88        .map_err(|_| Error::AppRegistrationError("Unable to parse response from server"))?;
89
90    debug!(
91        "App registration done, client_id={}",
92        registered_app.client_id
93    );
94
95    Ok(registered_app)
96}