myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

mod.rs (4885B)


      1 pub mod backend;
      2 pub mod runtime;
      3 
      4 use crate::config::MycConfig;
      5 use crate::error::MycError;
      6 
      7 pub use backend::MycSignerBackend;
      8 pub use runtime::{MycRuntime, MycRuntimePaths, MycSignerContext, MycStartupSnapshot};
      9 
     10 #[derive(Clone)]
     11 pub struct MycApp {
     12     runtime: MycRuntime,
     13 }
     14 
     15 impl MycApp {
     16     pub fn bootstrap(config: MycConfig) -> Result<Self, MycError> {
     17         Ok(Self {
     18             runtime: MycRuntime::bootstrap(config)?,
     19         })
     20     }
     21 
     22     pub fn runtime(&self) -> &MycRuntime {
     23         &self.runtime
     24     }
     25 
     26     pub fn snapshot(&self) -> MycStartupSnapshot {
     27         self.runtime.snapshot()
     28     }
     29 
     30     pub async fn run(self) -> Result<(), MycError> {
     31         self.runtime.run().await
     32     }
     33 
     34     pub async fn run_until<F>(self, shutdown: F) -> Result<(), MycError>
     35     where
     36         F: std::future::Future<Output = ()>,
     37     {
     38         self.runtime.run_until(shutdown).await
     39     }
     40 }
     41 
     42 #[cfg(test)]
     43 mod tests {
     44     use std::path::PathBuf;
     45 
     46     use radroots_identity::RadrootsIdentity;
     47 
     48     use crate::config::{MycConfig, MycSignerStateBackend};
     49 
     50     use super::MycApp;
     51 
     52     fn write_test_identity(path: &std::path::Path, secret_key: &str) {
     53         let identity =
     54             RadrootsIdentity::from_secret_key_str(secret_key).expect("identity from secret");
     55         crate::identity_files::store_encrypted_identity(path, &identity).expect("write identity");
     56     }
     57 
     58     #[test]
     59     fn app_bootstrap_preserves_runtime_snapshot() {
     60         let temp = tempfile::tempdir().expect("tempdir");
     61         let mut config = MycConfig::default();
     62         config.paths.state_dir = PathBuf::from(temp.path()).join("state");
     63         config.paths.signer_identity_path = temp.path().join("identity.json");
     64         config.paths.user_identity_path = temp.path().join("user.json");
     65         write_test_identity(
     66             &config.paths.signer_identity_path,
     67             "1111111111111111111111111111111111111111111111111111111111111111",
     68         );
     69         write_test_identity(
     70             &config.paths.user_identity_path,
     71             "2222222222222222222222222222222222222222222222222222222222222222",
     72         );
     73 
     74         let app = MycApp::bootstrap(config).expect("bootstrap");
     75         let snapshot = app.snapshot();
     76 
     77         assert!(snapshot.state_dir.ends_with("state"));
     78         assert!(snapshot.audit_dir.ends_with("audit"));
     79         assert!(
     80             snapshot
     81                 .signer_identity_path
     82                 .as_ref()
     83                 .expect("encrypted signer path")
     84                 .ends_with("identity.json")
     85         );
     86         assert!(
     87             snapshot
     88                 .user_identity_path
     89                 .as_ref()
     90                 .expect("encrypted user path")
     91                 .ends_with("user.json")
     92         );
     93         assert_eq!(
     94             snapshot.signer_identity_source.backend.as_str(),
     95             "encrypted_file"
     96         );
     97         assert_eq!(
     98             snapshot.user_identity_source.backend.as_str(),
     99             "encrypted_file"
    100         );
    101         assert_eq!(snapshot.signer_state_backend.as_str(), "json_file");
    102         assert!(snapshot.signer_state_path.ends_with("signer-state.json"));
    103         assert_eq!(snapshot.runtime_audit_backend.as_str(), "jsonl_file");
    104         assert!(snapshot.runtime_audit_path.ends_with("operations.jsonl"));
    105         assert!(!snapshot.signer_identity_id.is_empty());
    106         assert!(!snapshot.signer_public_key_hex.is_empty());
    107         assert!(!snapshot.user_identity_id.is_empty());
    108         assert!(!snapshot.user_public_key_hex.is_empty());
    109         assert!(!snapshot.transport.enabled);
    110     }
    111 
    112     #[test]
    113     fn app_bootstrap_uses_backend_aware_signer_state_path() {
    114         let temp = tempfile::tempdir().expect("tempdir");
    115         let mut config = MycConfig::default();
    116         config.paths.state_dir = PathBuf::from(temp.path()).join("state");
    117         config.paths.signer_identity_path = temp.path().join("identity.json");
    118         config.paths.user_identity_path = temp.path().join("user.json");
    119         config.persistence.signer_state_backend = MycSignerStateBackend::Sqlite;
    120         config.persistence.runtime_audit_backend = crate::config::MycRuntimeAuditBackend::Sqlite;
    121         write_test_identity(
    122             &config.paths.signer_identity_path,
    123             "1111111111111111111111111111111111111111111111111111111111111111",
    124         );
    125         write_test_identity(
    126             &config.paths.user_identity_path,
    127             "2222222222222222222222222222222222222222222222222222222222222222",
    128         );
    129 
    130         let app = MycApp::bootstrap(config).expect("bootstrap");
    131         let snapshot = app.snapshot();
    132 
    133         assert_eq!(snapshot.signer_state_backend.as_str(), "sqlite");
    134         assert!(snapshot.signer_state_path.ends_with("signer-state.sqlite"));
    135         assert_eq!(snapshot.runtime_audit_backend.as_str(), "sqlite");
    136         assert!(snapshot.runtime_audit_path.ends_with("operations.sqlite"));
    137     }
    138 }