create-ekka-desktop-app 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ekka-desktop-app",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Create an EKKA desktop app with built-in demo backend. No setup required.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,7 @@ use std::path::PathBuf;
10
10
  use std::time::{SystemTime, UNIX_EPOCH};
11
11
 
12
12
  /// Check if a valid HOME grant exists for the given auth context
13
- pub fn check_home_grant(home_path: &PathBuf, auth: &AuthContext) -> Result<bool, String> {
13
+ pub fn check_home_grant(home_path: &PathBuf, auth: &AuthContext, verify_key: &str) -> Result<bool, String> {
14
14
  let grants_path = home_path.join("grants.json");
15
15
 
16
16
  // No grants file = no grant
@@ -18,11 +18,7 @@ pub fn check_home_grant(home_path: &PathBuf, auth: &AuthContext) -> Result<bool,
18
18
  return Ok(false);
19
19
  }
20
20
 
21
- // Load engine verify key
22
- let key_b64 = match std::env::var("ENGINE_GRANT_VERIFY_KEY_B64") {
23
- Ok(k) => k,
24
- Err(_) => return Err("ENGINE_GRANT_VERIFY_KEY_B64 not set".to_string()),
25
- };
21
+ let key_b64 = verify_key;
26
22
 
27
23
  // Load and verify grants
28
24
  let store = GrantStore::new(grants_path, &key_b64).map_err(|e| e.to_string())?;
@@ -101,8 +97,21 @@ pub fn get_home_status(state: &EngineState) -> (HomeState, PathBuf, bool, Option
101
97
  None => return (HomeState::BootstrapPreLogin, home_path, false, None),
102
98
  };
103
99
 
100
+ // Get verify key from state (fetched from well-known endpoint)
101
+ let verify_key = match state.get_grant_verify_key() {
102
+ Some(k) => k,
103
+ None => {
104
+ return (
105
+ HomeState::AuthenticatedNoHomeGrant,
106
+ home_path,
107
+ false,
108
+ Some("Grant verification key not loaded. Waiting for engine configuration.".to_string()),
109
+ );
110
+ }
111
+ };
112
+
104
113
  // Check for valid HOME grant
105
- match check_home_grant(&home_path, &auth) {
114
+ match check_home_grant(&home_path, &auth, &verify_key) {
106
115
  Ok(true) => (HomeState::HomeGranted, home_path, true, None),
107
116
  Ok(false) => (
108
117
  HomeState::AuthenticatedNoHomeGrant,
@@ -11,6 +11,7 @@ mod config;
11
11
  mod device_secret;
12
12
  mod engine_process;
13
13
  mod grants;
14
+ mod well_known;
14
15
  mod handlers;
15
16
  mod node_auth;
16
17
  mod node_credentials;
@@ -30,14 +31,11 @@ use tauri::Manager;
30
31
 
31
32
  fn main() {
32
33
  // Load .env.local for development (before anything else)
33
- // This provides ENGINE_GRANT_VERIFY_KEY_B64, EKKA_SECURITY_EPOCH, etc.
34
- if let Err(e) = dotenvy::from_filename(".env.local") {
34
+ // This provides EKKA_SECURITY_EPOCH and other dev-time overrides.
35
+ // Note: ENGINE_GRANT_VERIFY_KEY_B64 is now fetched from /.well-known/ekka-configuration
36
+ if let Err(_) = dotenvy::from_filename(".env.local") {
35
37
  // Also try parent directory (when running from src-tauri)
36
38
  let _ = dotenvy::from_filename("../.env.local");
37
- // Silence error in production where .env.local may not exist
38
- if std::env::var("ENGINE_GRANT_VERIFY_KEY_B64").is_err() {
39
- eprintln!("Warning: .env.local not loaded and ENGINE_GRANT_VERIFY_KEY_B64 not set: {}", e);
40
- }
41
39
  }
42
40
 
43
41
  // Initialize tracing for runner logs
@@ -65,14 +63,12 @@ fn main() {
65
63
  // Attempt to spawn engine process
66
64
  tracing::info!(op = "desktop.startup", "EKKA Desktop starting");
67
65
 
68
- // Log required env vars status
69
- let grant_key_set = std::env::var("ENGINE_GRANT_VERIFY_KEY_B64").is_ok();
66
+ // Log security epoch status (still needed for home bootstrap)
70
67
  let security_epoch_set = std::env::var("EKKA_SECURITY_EPOCH").is_ok();
71
68
  tracing::info!(
72
69
  op = "desktop.required_env.loaded",
73
- ENGINE_GRANT_VERIFY_KEY_B64 = grant_key_set,
74
70
  EKKA_SECURITY_EPOCH = security_epoch_set,
75
- "Required security env vars"
71
+ "Security epoch env var status"
76
72
  );
77
73
 
78
74
  // Log build-time baked engine URL presence (not the URL itself)
@@ -107,6 +103,28 @@ fn main() {
107
103
  let node_auth_holder = state_handle.node_auth_token.clone();
108
104
  let node_auth_state = state_handle.node_auth_state.clone();
109
105
 
106
+ // Fetch grant verification key from engine's well-known endpoint
107
+ // This runs async and caches the key in state for grant verification
108
+ let app_handle = app.app_handle().clone();
109
+ tauri::async_runtime::spawn(async move {
110
+ let state = app_handle.state::<EngineState>();
111
+ match well_known::fetch_and_cache_verify_key(&state).await {
112
+ Ok(()) => {
113
+ tracing::info!(
114
+ op = "desktop.well_known.loaded",
115
+ "Grant verification key loaded from engine"
116
+ );
117
+ }
118
+ Err(e) => {
119
+ tracing::warn!(
120
+ op = "desktop.well_known.failed",
121
+ error = %e,
122
+ "Failed to fetch grant verification key - grants will not be verified"
123
+ );
124
+ }
125
+ }
126
+ });
127
+
110
128
  // Spawn engine in background thread to not block UI
111
129
  let engine = engine_for_setup.clone();
112
130
  std::thread::spawn(move || {
@@ -325,6 +325,8 @@ pub struct EngineState {
325
325
  pub node_auth_state: Arc<NodeAuthStateHolder>,
326
326
  /// External engine process (Phase 3A)
327
327
  pub engine_process: Option<Arc<EngineProcess>>,
328
+ /// Cached grant verification key (fetched from /.well-known/ekka-configuration)
329
+ pub grant_verify_key: RwLock<Option<String>>,
328
330
  }
329
331
 
330
332
  impl Default for EngineState {
@@ -341,6 +343,7 @@ impl Default for EngineState {
341
343
  node_auth_token: Arc::new(NodeAuthTokenHolder::new()),
342
344
  node_auth_state: Arc::new(NodeAuthStateHolder::new()),
343
345
  engine_process: None,
346
+ grant_verify_key: RwLock::new(None),
344
347
  }
345
348
  }
346
349
  }
@@ -360,6 +363,7 @@ impl EngineState {
360
363
  node_auth_token: Arc::new(NodeAuthTokenHolder::new()),
361
364
  node_auth_state: Arc::new(NodeAuthStateHolder::new()),
362
365
  engine_process: Some(engine),
366
+ grant_verify_key: RwLock::new(None),
363
367
  }
364
368
  }
365
369
 
@@ -431,6 +435,18 @@ impl EngineState {
431
435
  pub fn clear_vault_cache(&self) {
432
436
  self.vault_cache.clear();
433
437
  }
438
+
439
+ /// Get cached grant verification key
440
+ pub fn get_grant_verify_key(&self) -> Option<String> {
441
+ self.grant_verify_key.read().ok()?.clone()
442
+ }
443
+
444
+ /// Set grant verification key (fetched from well-known endpoint)
445
+ pub fn set_grant_verify_key(&self, key: String) {
446
+ if let Ok(mut guard) = self.grant_verify_key.write() {
447
+ *guard = Some(key);
448
+ }
449
+ }
434
450
  }
435
451
 
436
452
  // =============================================================================
@@ -0,0 +1,77 @@
1
+ //! Well-Known Configuration Fetcher
2
+ //!
3
+ //! Fetches public configuration from the EKKA Engine's /.well-known/ekka-configuration endpoint.
4
+ //! This includes the grant verification key needed for cryptographic grant validation.
5
+
6
+ use crate::config;
7
+ use serde::Deserialize;
8
+
9
+ /// Response from /.well-known/ekka-configuration
10
+ #[derive(Debug, Deserialize)]
11
+ pub struct WellKnownConfig {
12
+ pub grant_verify_key_b64: String,
13
+ pub grant_signing_algorithm: String,
14
+ #[serde(default)]
15
+ pub api_version: Option<String>,
16
+ }
17
+
18
+ /// Fetch the well-known configuration from the engine.
19
+ /// Returns the grant verification key (base64).
20
+ pub async fn fetch_grant_verify_key() -> Result<String, String> {
21
+ let engine_url = config::engine_url();
22
+ let url = format!("{}/engine/.well-known/ekka-configuration", engine_url.trim_end_matches('/'));
23
+
24
+ tracing::info!(
25
+ op = "well_known.fetch.start",
26
+ url = %url,
27
+ "Fetching grant verification key from engine"
28
+ );
29
+
30
+ let client = reqwest::Client::builder()
31
+ .timeout(std::time::Duration::from_secs(10))
32
+ .build()
33
+ .map_err(|e| format!("Failed to create HTTP client: {}", e))?;
34
+
35
+ let response = client
36
+ .get(&url)
37
+ .header("X-EKKA-CLIENT", config::app_slug())
38
+ .send()
39
+ .await
40
+ .map_err(|e| format!("Failed to fetch well-known config: {}", e))?;
41
+
42
+ if !response.status().is_success() {
43
+ return Err(format!(
44
+ "Engine returned error: {} {}",
45
+ response.status().as_u16(),
46
+ response.status().canonical_reason().unwrap_or("Unknown")
47
+ ));
48
+ }
49
+
50
+ let config: WellKnownConfig = response
51
+ .json()
52
+ .await
53
+ .map_err(|e| format!("Failed to parse well-known config: {}", e))?;
54
+
55
+ tracing::info!(
56
+ op = "well_known.fetch.success",
57
+ algorithm = %config.grant_signing_algorithm,
58
+ "Grant verification key fetched successfully"
59
+ );
60
+
61
+ Ok(config.grant_verify_key_b64)
62
+ }
63
+
64
+ /// Fetch and cache the grant verification key in state.
65
+ /// Also sets ENGINE_GRANT_VERIFY_KEY_B64 env var for SDK compatibility.
66
+ /// This is called on app startup.
67
+ pub async fn fetch_and_cache_verify_key(state: &crate::state::EngineState) -> Result<(), String> {
68
+ let key = fetch_grant_verify_key().await?;
69
+
70
+ // Cache in state
71
+ state.set_grant_verify_key(key.clone());
72
+
73
+ // Also set env var for SDK compatibility (ekka-ops, ekka-path-guard use it)
74
+ std::env::set_var("ENGINE_GRANT_VERIFY_KEY_B64", &key);
75
+
76
+ Ok(())
77
+ }