create-ekka-desktop-app 0.2.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.
Files changed (96) hide show
  1. package/README.md +137 -0
  2. package/bin/cli.js +72 -0
  3. package/package.json +23 -0
  4. package/template/branding/app.json +6 -0
  5. package/template/branding/icon.icns +0 -0
  6. package/template/eslint.config.js +98 -0
  7. package/template/index.html +29 -0
  8. package/template/package.json +40 -0
  9. package/template/src/app/App.tsx +24 -0
  10. package/template/src/demo/DemoApp.tsx +260 -0
  11. package/template/src/demo/components/Banner.tsx +82 -0
  12. package/template/src/demo/components/EmptyState.tsx +61 -0
  13. package/template/src/demo/components/InfoPopover.tsx +171 -0
  14. package/template/src/demo/components/InfoTooltip.tsx +76 -0
  15. package/template/src/demo/components/LearnMore.tsx +98 -0
  16. package/template/src/demo/components/NodeCredentialsOnboarding.tsx +219 -0
  17. package/template/src/demo/components/SetupWizard.tsx +48 -0
  18. package/template/src/demo/components/StatusBadge.tsx +83 -0
  19. package/template/src/demo/components/index.ts +10 -0
  20. package/template/src/demo/hooks/index.ts +6 -0
  21. package/template/src/demo/hooks/useAuditEvents.ts +30 -0
  22. package/template/src/demo/layout/Shell.tsx +110 -0
  23. package/template/src/demo/layout/Sidebar.tsx +192 -0
  24. package/template/src/demo/pages/AuditLogPage.tsx +235 -0
  25. package/template/src/demo/pages/DocGenPage.tsx +874 -0
  26. package/template/src/demo/pages/HomeSetupPage.tsx +182 -0
  27. package/template/src/demo/pages/LoginPage.tsx +192 -0
  28. package/template/src/demo/pages/PathPermissionsPage.tsx +873 -0
  29. package/template/src/demo/pages/RunnerPage.tsx +445 -0
  30. package/template/src/demo/pages/SystemPage.tsx +557 -0
  31. package/template/src/demo/pages/VaultPage.tsx +805 -0
  32. package/template/src/ekka/__tests__/demo-backend.test.ts +187 -0
  33. package/template/src/ekka/audit/index.ts +7 -0
  34. package/template/src/ekka/audit/store.ts +68 -0
  35. package/template/src/ekka/audit/types.ts +22 -0
  36. package/template/src/ekka/auth/client.ts +212 -0
  37. package/template/src/ekka/auth/index.ts +30 -0
  38. package/template/src/ekka/auth/storage.ts +114 -0
  39. package/template/src/ekka/auth/types.ts +67 -0
  40. package/template/src/ekka/backend/demo.ts +151 -0
  41. package/template/src/ekka/backend/interface.ts +36 -0
  42. package/template/src/ekka/config.ts +48 -0
  43. package/template/src/ekka/constants.ts +143 -0
  44. package/template/src/ekka/errors.ts +54 -0
  45. package/template/src/ekka/index.ts +516 -0
  46. package/template/src/ekka/internal/backend.ts +156 -0
  47. package/template/src/ekka/internal/index.ts +7 -0
  48. package/template/src/ekka/ops/auth.ts +29 -0
  49. package/template/src/ekka/ops/debug.ts +68 -0
  50. package/template/src/ekka/ops/home.ts +101 -0
  51. package/template/src/ekka/ops/index.ts +16 -0
  52. package/template/src/ekka/ops/nodeCredentials.ts +131 -0
  53. package/template/src/ekka/ops/nodeSession.ts +145 -0
  54. package/template/src/ekka/ops/paths.ts +183 -0
  55. package/template/src/ekka/ops/runner.ts +86 -0
  56. package/template/src/ekka/ops/runtime.ts +31 -0
  57. package/template/src/ekka/ops/setup.ts +47 -0
  58. package/template/src/ekka/ops/vault.ts +459 -0
  59. package/template/src/ekka/ops/workflowRuns.ts +116 -0
  60. package/template/src/ekka/types.ts +82 -0
  61. package/template/src/ekka/utils/idempotency.ts +14 -0
  62. package/template/src/ekka/utils/index.ts +7 -0
  63. package/template/src/ekka/utils/time.ts +77 -0
  64. package/template/src/main.tsx +12 -0
  65. package/template/src/vite-env.d.ts +12 -0
  66. package/template/src-tauri/Cargo.toml +41 -0
  67. package/template/src-tauri/build.rs +3 -0
  68. package/template/src-tauri/capabilities/default.json +11 -0
  69. package/template/src-tauri/icons/icon.icns +0 -0
  70. package/template/src-tauri/icons/icon.png +0 -0
  71. package/template/src-tauri/resources/ekka-engine-bootstrap +0 -0
  72. package/template/src-tauri/src/bootstrap.rs +37 -0
  73. package/template/src-tauri/src/commands.rs +1215 -0
  74. package/template/src-tauri/src/device_secret.rs +111 -0
  75. package/template/src-tauri/src/engine_process.rs +538 -0
  76. package/template/src-tauri/src/grants.rs +129 -0
  77. package/template/src-tauri/src/handlers/home.rs +65 -0
  78. package/template/src-tauri/src/handlers/mod.rs +7 -0
  79. package/template/src-tauri/src/handlers/paths.rs +128 -0
  80. package/template/src-tauri/src/handlers/vault.rs +680 -0
  81. package/template/src-tauri/src/main.rs +243 -0
  82. package/template/src-tauri/src/node_auth.rs +858 -0
  83. package/template/src-tauri/src/node_credentials.rs +541 -0
  84. package/template/src-tauri/src/node_runner.rs +882 -0
  85. package/template/src-tauri/src/node_vault_crypto.rs +113 -0
  86. package/template/src-tauri/src/node_vault_store.rs +267 -0
  87. package/template/src-tauri/src/ops/auth.rs +50 -0
  88. package/template/src-tauri/src/ops/home.rs +251 -0
  89. package/template/src-tauri/src/ops/mod.rs +7 -0
  90. package/template/src-tauri/src/ops/runtime.rs +21 -0
  91. package/template/src-tauri/src/state.rs +639 -0
  92. package/template/src-tauri/src/types.rs +84 -0
  93. package/template/src-tauri/tauri.conf.json +41 -0
  94. package/template/tsconfig.json +26 -0
  95. package/template/tsconfig.tsbuildinfo +1 -0
  96. package/template/vite.config.ts +34 -0
@@ -0,0 +1,129 @@
1
+ //! Grant validation
2
+ //!
3
+ //! Handles verification of HOME grants and home status checks.
4
+
5
+ use crate::bootstrap::resolve_home_path;
6
+ use crate::state::{AuthContext, EngineState, HomeState};
7
+ use crate::types::EngineResponse;
8
+ use ekka_sdk_core::ekka_path_guard::GrantStore;
9
+ use std::path::PathBuf;
10
+ use std::time::{SystemTime, UNIX_EPOCH};
11
+
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> {
14
+ let grants_path = home_path.join("grants.json");
15
+
16
+ // No grants file = no grant
17
+ if !grants_path.exists() {
18
+ return Ok(false);
19
+ }
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
+ };
26
+
27
+ // Load and verify grants
28
+ let store = GrantStore::new(grants_path, &key_b64).map_err(|e| e.to_string())?;
29
+ let grants = store.grants();
30
+
31
+ // Check for valid HOME grant matching auth context
32
+ let now = SystemTime::now()
33
+ .duration_since(UNIX_EPOCH)
34
+ .unwrap_or_default()
35
+ .as_secs() as i64;
36
+
37
+ for grant in grants {
38
+ // Check if this is a HOME grant (covers home_path)
39
+ let home_str = home_path.to_string_lossy();
40
+ if !home_str.starts_with(grant.path_prefix()) && grant.path_prefix() != home_str {
41
+ continue;
42
+ }
43
+
44
+ // Check tenant_id matches
45
+ if grant.tenant_id() != auth.tenant_id {
46
+ continue;
47
+ }
48
+
49
+ // Check sub matches
50
+ if grant.subject() != auth.sub {
51
+ continue;
52
+ }
53
+
54
+ // Check not expired
55
+ if grant.expires_at() < now {
56
+ continue;
57
+ }
58
+
59
+ // Valid HOME grant found
60
+ return Ok(true);
61
+ }
62
+
63
+ Ok(false)
64
+ }
65
+
66
+ /// Get current home status including state, path, and grant presence
67
+ pub fn get_home_status(state: &EngineState) -> (HomeState, PathBuf, bool, Option<String>) {
68
+ // Resolve home path
69
+ let home_path = match resolve_home_path() {
70
+ Ok(p) => p,
71
+ Err(e) => {
72
+ return (
73
+ HomeState::BootstrapPreLogin,
74
+ PathBuf::new(),
75
+ false,
76
+ Some(format!("Failed to resolve home path: {}", e)),
77
+ );
78
+ }
79
+ };
80
+
81
+ // Store home path
82
+ if let Ok(mut hp) = state.home_path.lock() {
83
+ *hp = Some(home_path.clone());
84
+ }
85
+
86
+ // Check auth
87
+ let auth = match state.auth.lock() {
88
+ Ok(guard) => guard.clone(),
89
+ Err(_) => {
90
+ return (
91
+ HomeState::BootstrapPreLogin,
92
+ home_path,
93
+ false,
94
+ Some("Lock error".to_string()),
95
+ )
96
+ }
97
+ };
98
+
99
+ let auth = match auth {
100
+ Some(a) => a,
101
+ None => return (HomeState::BootstrapPreLogin, home_path, false, None),
102
+ };
103
+
104
+ // Check for valid HOME grant
105
+ match check_home_grant(&home_path, &auth) {
106
+ Ok(true) => (HomeState::HomeGranted, home_path, true, None),
107
+ Ok(false) => (
108
+ HomeState::AuthenticatedNoHomeGrant,
109
+ home_path,
110
+ false,
111
+ Some("No valid HOME grant found".to_string()),
112
+ ),
113
+ Err(e) => (HomeState::AuthenticatedNoHomeGrant, home_path, false, Some(e)),
114
+ }
115
+ }
116
+
117
+ /// Guard function: returns error response if HOME grant is not present
118
+ pub fn require_home_granted(state: &EngineState) -> Result<(), EngineResponse> {
119
+ let (home_state, _, _, reason) = get_home_status(state);
120
+
121
+ if home_state != HomeState::HomeGranted {
122
+ return Err(EngineResponse::err(
123
+ "HOME_GRANT_REQUIRED",
124
+ &reason.unwrap_or_else(|| "HOME grant required before this operation".to_string()),
125
+ ));
126
+ }
127
+
128
+ Ok(())
129
+ }
@@ -0,0 +1,65 @@
1
+ //! Home operation handlers
2
+ //!
3
+ //! Thin wrappers that call SDK operations.
4
+
5
+ use crate::state::{EngineHttpGrantIssuer, EngineState};
6
+ use crate::types::EngineResponse;
7
+ use ekka_sdk_core::ekka_ops::home;
8
+ use serde_json::json;
9
+
10
+ /// Handle home.status operation
11
+ pub fn handle_status(state: &EngineState) -> EngineResponse {
12
+ let ctx = match state.to_runtime_context() {
13
+ Some(c) => c,
14
+ None => {
15
+ // No context yet - return pre-login state
16
+ let home_path = state
17
+ .home_path
18
+ .lock()
19
+ .ok()
20
+ .and_then(|p| p.clone())
21
+ .map(|p| p.to_string_lossy().to_string())
22
+ .unwrap_or_default();
23
+
24
+ return EngineResponse::ok(json!({
25
+ "state": "BOOTSTRAP_PRE_LOGIN",
26
+ "homePath": home_path,
27
+ "grantPresent": false,
28
+ "reason": null,
29
+ }));
30
+ }
31
+ };
32
+
33
+ let status = home::status(&ctx);
34
+
35
+ EngineResponse::ok(json!({
36
+ "state": status.state,
37
+ "homePath": status.home_path,
38
+ "grantPresent": status.grant_present,
39
+ "reason": status.reason,
40
+ }))
41
+ }
42
+
43
+ /// Handle home.grant operation
44
+ pub fn handle_grant(state: &EngineState) -> EngineResponse {
45
+ let ctx = match state.to_runtime_context() {
46
+ Some(c) => c,
47
+ None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
48
+ };
49
+
50
+ // Check auth
51
+ if ctx.auth.is_none() {
52
+ return EngineResponse::err("NOT_AUTHENTICATED", "Must call auth.set before home.grant");
53
+ }
54
+
55
+ let issuer = EngineHttpGrantIssuer::new();
56
+
57
+ match home::grant(&ctx, &issuer) {
58
+ Ok(result) => EngineResponse::ok(json!({
59
+ "success": result.success,
60
+ "grant_id": result.grant_id,
61
+ "expires_at": result.expires_at,
62
+ })),
63
+ Err(e) => EngineResponse::err(e.code, &e.message),
64
+ }
65
+ }
@@ -0,0 +1,7 @@
1
+ //! Thin handlers for SDK operations
2
+ //!
3
+ //! Each handler converts EngineState to RuntimeContext, calls SDK, converts result.
4
+
5
+ pub mod home;
6
+ pub mod paths;
7
+ pub mod vault;
@@ -0,0 +1,128 @@
1
+ //! Path operation handlers
2
+ //!
3
+ //! Thin wrappers that call SDK operations.
4
+
5
+ use crate::state::{EngineHttpGrantIssuer, EngineState};
6
+ use crate::types::EngineResponse;
7
+ use ekka_sdk_core::ekka_ops::{paths, PathAccess, PathType};
8
+ use serde_json::{json, Value};
9
+ use std::path::Path;
10
+
11
+ /// Handle paths.check operation
12
+ pub fn handle_check(payload: &Value, state: &EngineState) -> EngineResponse {
13
+ let ctx = match state.to_runtime_context() {
14
+ Some(c) => c,
15
+ None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
16
+ };
17
+
18
+ let path = match payload.get("path").and_then(|v| v.as_str()) {
19
+ Some(p) => p,
20
+ None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
21
+ };
22
+
23
+ let operation = payload
24
+ .get("operation")
25
+ .and_then(|v| v.as_str())
26
+ .unwrap_or("read");
27
+
28
+ let result = paths::check_detailed(&ctx, Path::new(path), operation);
29
+
30
+ EngineResponse::ok(json!({
31
+ "allowed": result.allowed,
32
+ "reason": result.reason,
33
+ "pathType": result.path_type,
34
+ "access": result.access,
35
+ }))
36
+ }
37
+
38
+ /// Handle paths.list operation
39
+ pub fn handle_list(payload: &Value, state: &EngineState) -> EngineResponse {
40
+ let ctx = match state.to_runtime_context() {
41
+ Some(c) => c,
42
+ None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
43
+ };
44
+
45
+ let path_type: Option<PathType> = payload
46
+ .get("pathType")
47
+ .and_then(|v| v.as_str())
48
+ .and_then(|s| serde_json::from_str(&format!("\"{}\"", s)).ok());
49
+
50
+ match paths::list(&ctx, path_type) {
51
+ Ok(paths_list) => EngineResponse::ok(json!({ "paths": paths_list })),
52
+ Err(e) => EngineResponse::err(e.code, &e.message),
53
+ }
54
+ }
55
+
56
+ /// Handle paths.get operation
57
+ pub fn handle_get(payload: &Value, state: &EngineState) -> EngineResponse {
58
+ let ctx = match state.to_runtime_context() {
59
+ Some(c) => c,
60
+ None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
61
+ };
62
+
63
+ let path = match payload.get("path").and_then(|v| v.as_str()) {
64
+ Some(p) => p,
65
+ None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
66
+ };
67
+
68
+ match paths::get(&ctx, Path::new(path)) {
69
+ Ok(Some(info)) => EngineResponse::ok(json!(info)),
70
+ Ok(None) => EngineResponse::ok(Value::Null),
71
+ Err(e) => EngineResponse::err(e.code, &e.message),
72
+ }
73
+ }
74
+
75
+ /// Handle paths.request operation
76
+ pub fn handle_request(payload: &Value, state: &EngineState) -> EngineResponse {
77
+ let ctx = match state.to_runtime_context() {
78
+ Some(c) => c,
79
+ None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
80
+ };
81
+
82
+ // Check auth
83
+ if ctx.auth.is_none() {
84
+ return EngineResponse::err("NOT_AUTHENTICATED", "Must login before requesting path access");
85
+ }
86
+
87
+ let path = match payload.get("path").and_then(|v| v.as_str()) {
88
+ Some(p) => p,
89
+ None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
90
+ };
91
+
92
+ let path_type: PathType = payload
93
+ .get("pathType")
94
+ .and_then(|v| v.as_str())
95
+ .and_then(|s| serde_json::from_str(&format!("\"{}\"", s)).ok())
96
+ .unwrap_or(PathType::General);
97
+
98
+ let access: PathAccess = payload
99
+ .get("access")
100
+ .and_then(|v| v.as_str())
101
+ .and_then(|s| serde_json::from_str(&format!("\"{}\"", s)).ok())
102
+ .unwrap_or(PathAccess::ReadOnly);
103
+
104
+ let issuer = EngineHttpGrantIssuer::new();
105
+
106
+ match paths::request(&ctx, &issuer, Path::new(path), path_type, access) {
107
+ Ok(result) => EngineResponse::ok(json!(result)),
108
+ Err(e) => EngineResponse::err(e.code, &e.message),
109
+ }
110
+ }
111
+
112
+ /// Handle paths.remove operation
113
+ pub fn handle_remove(payload: &Value, state: &EngineState) -> EngineResponse {
114
+ let ctx = match state.to_runtime_context() {
115
+ Some(c) => c,
116
+ None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
117
+ };
118
+
119
+ let path = match payload.get("path").and_then(|v| v.as_str()) {
120
+ Some(p) => p,
121
+ None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
122
+ };
123
+
124
+ match paths::remove(&ctx, Path::new(path)) {
125
+ Ok(removed) => EngineResponse::ok(json!({ "removed": removed })),
126
+ Err(e) => EngineResponse::err(e.code, &e.message),
127
+ }
128
+ }