cesium_oauth/apps/
file_cached.rs

1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4
5use async_trait::async_trait;
6use log::{debug, warn};
7use serde::*;
8
9use crate::apps;
10use crate::apps::{AppInfo, AppProvider, RegisteredApp};
11use crate::error::Error;
12
13#[derive(Serialize, Deserialize, Default)]
14struct AppsFile {
15    apps: HashMap<String, RegisteredApp>,
16}
17
18#[derive(Serialize, Debug)]
19struct AppCreateRequest {
20    client_name: String,
21    redirect_uris: String,
22    scopes: String,
23}
24
25/// [AppProvider] implementation that uses a [toml](https://toml.io/en/) file as a cache for storing apps. This
26/// will only work on platforms where the is a filesystem available.
27pub struct FileCachedAppProvider {
28    file_path: String,
29    cache: AppsFile,
30    app: AppInfo,
31}
32
33impl FileCachedAppProvider {
34    /// Create a new provider, using a specific file for storing the [RegisteredApp]s.
35    pub fn new(filename: &str, app: AppInfo) -> Result<Self, Error> {
36        let cache = Self::read_apps_file(filename)?;
37        debug!("Cache contains {} app(s)", cache.apps.len());
38        Ok(Self {
39            file_path: filename.to_owned(),
40            cache,
41            app,
42        })
43    }
44
45    fn read_apps_file(file_path: &str) -> Result<AppsFile, Error> {
46        debug!("Loading apps file `{file_path}`");
47        if Path::new(file_path).exists() {
48            fs::read_to_string(file_path)
49                .map_err(|error| Error::AppCacheIoError(error))
50                .and_then(|contents| {
51                    toml::from_str(&contents).map_err(|error| Error::AppCacheInvalid(error))
52                })
53        } else {
54            debug!("Apps file not found, using blank apps cache");
55            Ok(AppsFile::default())
56        }
57    }
58
59    fn save_apps_file(&self) -> Result<(), Error> {
60        debug!("Saving apps cache to `{}`", self.file_path);
61        let contents = toml::to_string(&self.cache).expect("Unable to serialize app cache");
62        fs::write(&self.file_path, &contents).map_err(|error| Error::AppCacheIoError(error))
63    }
64}
65
66#[async_trait]
67impl AppProvider for FileCachedAppProvider {
68    async fn get_app_for(&mut self, instance_domain: &str) -> Result<RegisteredApp, Error> {
69        debug!("Get registered app for `{instance_domain}`");
70        if let Some(registered_app) = self.cache.apps.get(instance_domain) {
71            debug!(
72                "App found in cache (client_id={})",
73                registered_app.client_id
74            );
75            Ok(registered_app.clone())
76        } else if let Ok(registered_app) = apps::register_new_app(instance_domain, &self.app).await
77        {
78            debug!(
79                "New app registered (client_id={}), adding to cache",
80                registered_app.client_id
81            );
82            self.cache
83                .apps
84                .insert(instance_domain.to_owned(), registered_app.clone());
85            self.save_apps_file()?;
86            Ok(registered_app.clone())
87        } else {
88            warn!("Unable to register new app to `{instance_domain}`");
89            Err(Error::AppRegistrationError("Unable to register app"))
90        }
91    }
92}