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.
- package/README.md +137 -0
- package/bin/cli.js +72 -0
- package/package.json +23 -0
- package/template/branding/app.json +6 -0
- package/template/branding/icon.icns +0 -0
- package/template/eslint.config.js +98 -0
- package/template/index.html +29 -0
- package/template/package.json +40 -0
- package/template/src/app/App.tsx +24 -0
- package/template/src/demo/DemoApp.tsx +260 -0
- package/template/src/demo/components/Banner.tsx +82 -0
- package/template/src/demo/components/EmptyState.tsx +61 -0
- package/template/src/demo/components/InfoPopover.tsx +171 -0
- package/template/src/demo/components/InfoTooltip.tsx +76 -0
- package/template/src/demo/components/LearnMore.tsx +98 -0
- package/template/src/demo/components/NodeCredentialsOnboarding.tsx +219 -0
- package/template/src/demo/components/SetupWizard.tsx +48 -0
- package/template/src/demo/components/StatusBadge.tsx +83 -0
- package/template/src/demo/components/index.ts +10 -0
- package/template/src/demo/hooks/index.ts +6 -0
- package/template/src/demo/hooks/useAuditEvents.ts +30 -0
- package/template/src/demo/layout/Shell.tsx +110 -0
- package/template/src/demo/layout/Sidebar.tsx +192 -0
- package/template/src/demo/pages/AuditLogPage.tsx +235 -0
- package/template/src/demo/pages/DocGenPage.tsx +874 -0
- package/template/src/demo/pages/HomeSetupPage.tsx +182 -0
- package/template/src/demo/pages/LoginPage.tsx +192 -0
- package/template/src/demo/pages/PathPermissionsPage.tsx +873 -0
- package/template/src/demo/pages/RunnerPage.tsx +445 -0
- package/template/src/demo/pages/SystemPage.tsx +557 -0
- package/template/src/demo/pages/VaultPage.tsx +805 -0
- package/template/src/ekka/__tests__/demo-backend.test.ts +187 -0
- package/template/src/ekka/audit/index.ts +7 -0
- package/template/src/ekka/audit/store.ts +68 -0
- package/template/src/ekka/audit/types.ts +22 -0
- package/template/src/ekka/auth/client.ts +212 -0
- package/template/src/ekka/auth/index.ts +30 -0
- package/template/src/ekka/auth/storage.ts +114 -0
- package/template/src/ekka/auth/types.ts +67 -0
- package/template/src/ekka/backend/demo.ts +151 -0
- package/template/src/ekka/backend/interface.ts +36 -0
- package/template/src/ekka/config.ts +48 -0
- package/template/src/ekka/constants.ts +143 -0
- package/template/src/ekka/errors.ts +54 -0
- package/template/src/ekka/index.ts +516 -0
- package/template/src/ekka/internal/backend.ts +156 -0
- package/template/src/ekka/internal/index.ts +7 -0
- package/template/src/ekka/ops/auth.ts +29 -0
- package/template/src/ekka/ops/debug.ts +68 -0
- package/template/src/ekka/ops/home.ts +101 -0
- package/template/src/ekka/ops/index.ts +16 -0
- package/template/src/ekka/ops/nodeCredentials.ts +131 -0
- package/template/src/ekka/ops/nodeSession.ts +145 -0
- package/template/src/ekka/ops/paths.ts +183 -0
- package/template/src/ekka/ops/runner.ts +86 -0
- package/template/src/ekka/ops/runtime.ts +31 -0
- package/template/src/ekka/ops/setup.ts +47 -0
- package/template/src/ekka/ops/vault.ts +459 -0
- package/template/src/ekka/ops/workflowRuns.ts +116 -0
- package/template/src/ekka/types.ts +82 -0
- package/template/src/ekka/utils/idempotency.ts +14 -0
- package/template/src/ekka/utils/index.ts +7 -0
- package/template/src/ekka/utils/time.ts +77 -0
- package/template/src/main.tsx +12 -0
- package/template/src/vite-env.d.ts +12 -0
- package/template/src-tauri/Cargo.toml +41 -0
- package/template/src-tauri/build.rs +3 -0
- package/template/src-tauri/capabilities/default.json +11 -0
- package/template/src-tauri/icons/icon.icns +0 -0
- package/template/src-tauri/icons/icon.png +0 -0
- package/template/src-tauri/resources/ekka-engine-bootstrap +0 -0
- package/template/src-tauri/src/bootstrap.rs +37 -0
- package/template/src-tauri/src/commands.rs +1215 -0
- package/template/src-tauri/src/device_secret.rs +111 -0
- package/template/src-tauri/src/engine_process.rs +538 -0
- package/template/src-tauri/src/grants.rs +129 -0
- package/template/src-tauri/src/handlers/home.rs +65 -0
- package/template/src-tauri/src/handlers/mod.rs +7 -0
- package/template/src-tauri/src/handlers/paths.rs +128 -0
- package/template/src-tauri/src/handlers/vault.rs +680 -0
- package/template/src-tauri/src/main.rs +243 -0
- package/template/src-tauri/src/node_auth.rs +858 -0
- package/template/src-tauri/src/node_credentials.rs +541 -0
- package/template/src-tauri/src/node_runner.rs +882 -0
- package/template/src-tauri/src/node_vault_crypto.rs +113 -0
- package/template/src-tauri/src/node_vault_store.rs +267 -0
- package/template/src-tauri/src/ops/auth.rs +50 -0
- package/template/src-tauri/src/ops/home.rs +251 -0
- package/template/src-tauri/src/ops/mod.rs +7 -0
- package/template/src-tauri/src/ops/runtime.rs +21 -0
- package/template/src-tauri/src/state.rs +639 -0
- package/template/src-tauri/src/types.rs +84 -0
- package/template/src-tauri/tauri.conf.json +41 -0
- package/template/tsconfig.json +26 -0
- package/template/tsconfig.tsbuildinfo +1 -0
- package/template/vite.config.ts +34 -0
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
//! Vault operation handlers
|
|
2
|
+
//!
|
|
3
|
+
//! Thin wrappers that call SDK vault operations.
|
|
4
|
+
//! Each handler is 5-15 lines max per architecture rules.
|
|
5
|
+
|
|
6
|
+
use crate::state::EngineState;
|
|
7
|
+
use crate::types::EngineResponse;
|
|
8
|
+
use ekka_sdk_core::ekka_ops::vault;
|
|
9
|
+
use serde_json::{json, Value};
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Status Handlers
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
/// Handle vault.status
|
|
16
|
+
pub fn handle_status(state: &EngineState) -> EngineResponse {
|
|
17
|
+
let ctx = match state.to_runtime_context() {
|
|
18
|
+
Some(c) => c,
|
|
19
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
match vault::status(&ctx) {
|
|
23
|
+
Ok(status) => EngineResponse::ok(json!(status)),
|
|
24
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Handle vault.capabilities
|
|
29
|
+
pub fn handle_capabilities(state: &EngineState) -> EngineResponse {
|
|
30
|
+
let ctx = match state.to_runtime_context() {
|
|
31
|
+
Some(c) => c,
|
|
32
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
match vault::capabilities(&ctx) {
|
|
36
|
+
Ok(caps) => EngineResponse::ok(json!(caps)),
|
|
37
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Secrets Handlers
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
/// Handle vault.secrets.list
|
|
46
|
+
pub fn handle_secrets_list(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
47
|
+
let ctx = match state.to_runtime_context() {
|
|
48
|
+
Some(c) => c,
|
|
49
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let opts: Option<vault::SecretListOptions> = payload
|
|
53
|
+
.get("opts")
|
|
54
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
55
|
+
|
|
56
|
+
match vault::secrets::list(&ctx, state.vault_cache(), opts) {
|
|
57
|
+
Ok(secrets) => EngineResponse::ok(json!({ "secrets": secrets })),
|
|
58
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Handle vault.secrets.get
|
|
63
|
+
pub fn handle_secrets_get(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
64
|
+
let ctx = match state.to_runtime_context() {
|
|
65
|
+
Some(c) => c,
|
|
66
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let id = match payload.get("id").and_then(|v| v.as_str()) {
|
|
70
|
+
Some(id) => id,
|
|
71
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'id'"),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
match vault::secrets::get(&ctx, state.vault_cache(), id) {
|
|
75
|
+
Ok(secret) => EngineResponse::ok(json!(secret)),
|
|
76
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Handle vault.secrets.create
|
|
81
|
+
pub fn handle_secrets_create(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
82
|
+
let ctx = match state.to_runtime_context() {
|
|
83
|
+
Some(c) => c,
|
|
84
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let input: vault::SecretCreateInput = match serde_json::from_value(payload.clone()) {
|
|
88
|
+
Ok(i) => i,
|
|
89
|
+
Err(e) => return EngineResponse::err("INVALID_PAYLOAD", &e.to_string()),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
match vault::secrets::create(&ctx, state.vault_cache(), input) {
|
|
93
|
+
Ok(secret) => EngineResponse::ok(json!(secret)),
|
|
94
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Handle vault.secrets.update
|
|
99
|
+
pub fn handle_secrets_update(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
100
|
+
let ctx = match state.to_runtime_context() {
|
|
101
|
+
Some(c) => c,
|
|
102
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
let id = match payload.get("id").and_then(|v| v.as_str()) {
|
|
106
|
+
Some(id) => id,
|
|
107
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'id'"),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
let input: vault::SecretUpdateInput = match payload.get("input")
|
|
111
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
|
112
|
+
{
|
|
113
|
+
Some(i) => i,
|
|
114
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing or invalid 'input'"),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
match vault::secrets::update(&ctx, state.vault_cache(), id, input) {
|
|
118
|
+
Ok(secret) => EngineResponse::ok(json!(secret)),
|
|
119
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Handle vault.secrets.delete
|
|
124
|
+
pub fn handle_secrets_delete(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
125
|
+
let ctx = match state.to_runtime_context() {
|
|
126
|
+
Some(c) => c,
|
|
127
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
let id = match payload.get("id").and_then(|v| v.as_str()) {
|
|
131
|
+
Some(id) => id,
|
|
132
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'id'"),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
match vault::secrets::delete(&ctx, state.vault_cache(), id) {
|
|
136
|
+
Ok(deleted) => EngineResponse::ok(json!({ "deleted": deleted })),
|
|
137
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Handle vault.secrets.upsert
|
|
142
|
+
pub fn handle_secrets_upsert(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
143
|
+
let ctx = match state.to_runtime_context() {
|
|
144
|
+
Some(c) => c,
|
|
145
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let input: vault::SecretCreateInput = match serde_json::from_value(payload.clone()) {
|
|
149
|
+
Ok(i) => i,
|
|
150
|
+
Err(e) => return EngineResponse::err("INVALID_PAYLOAD", &e.to_string()),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
match vault::secrets::upsert(&ctx, state.vault_cache(), input) {
|
|
154
|
+
Ok(secret) => EngineResponse::ok(json!(secret)),
|
|
155
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// Bundles Handlers
|
|
161
|
+
// =============================================================================
|
|
162
|
+
|
|
163
|
+
/// Handle vault.bundles.list
|
|
164
|
+
pub fn handle_bundles_list(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
165
|
+
let ctx = match state.to_runtime_context() {
|
|
166
|
+
Some(c) => c,
|
|
167
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
let opts: Option<vault::BundleListOptions> = payload
|
|
171
|
+
.get("opts")
|
|
172
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
173
|
+
|
|
174
|
+
match vault::bundles::list(&ctx, state.vault_cache(), opts) {
|
|
175
|
+
Ok(bundles) => EngineResponse::ok(json!({ "bundles": bundles })),
|
|
176
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Handle vault.bundles.get
|
|
181
|
+
pub fn handle_bundles_get(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
182
|
+
let ctx = match state.to_runtime_context() {
|
|
183
|
+
Some(c) => c,
|
|
184
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
let id = match payload.get("id").and_then(|v| v.as_str()) {
|
|
188
|
+
Some(id) => id,
|
|
189
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'id'"),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
match vault::bundles::get(&ctx, state.vault_cache(), id) {
|
|
193
|
+
Ok(bundle) => EngineResponse::ok(json!(bundle)),
|
|
194
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Handle vault.bundles.create
|
|
199
|
+
pub fn handle_bundles_create(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
200
|
+
let ctx = match state.to_runtime_context() {
|
|
201
|
+
Some(c) => c,
|
|
202
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
let input: vault::BundleCreateInput = match serde_json::from_value(payload.clone()) {
|
|
206
|
+
Ok(i) => i,
|
|
207
|
+
Err(e) => return EngineResponse::err("INVALID_PAYLOAD", &e.to_string()),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
match vault::bundles::create(&ctx, state.vault_cache(), input) {
|
|
211
|
+
Ok(bundle) => EngineResponse::ok(json!(bundle)),
|
|
212
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Handle vault.bundles.rename
|
|
217
|
+
pub fn handle_bundles_rename(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
218
|
+
let ctx = match state.to_runtime_context() {
|
|
219
|
+
Some(c) => c,
|
|
220
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
let id = match payload.get("id").and_then(|v| v.as_str()) {
|
|
224
|
+
Some(id) => id,
|
|
225
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'id'"),
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
let name = match payload.get("name").and_then(|v| v.as_str()) {
|
|
229
|
+
Some(n) => n,
|
|
230
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'name'"),
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
match vault::bundles::rename(&ctx, state.vault_cache(), id, name) {
|
|
234
|
+
Ok(bundle) => EngineResponse::ok(json!(bundle)),
|
|
235
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Handle vault.bundles.delete
|
|
240
|
+
pub fn handle_bundles_delete(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
241
|
+
let ctx = match state.to_runtime_context() {
|
|
242
|
+
Some(c) => c,
|
|
243
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
let id = match payload.get("id").and_then(|v| v.as_str()) {
|
|
247
|
+
Some(id) => id,
|
|
248
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'id'"),
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
match vault::bundles::delete(&ctx, state.vault_cache(), id) {
|
|
252
|
+
Ok(deleted) => EngineResponse::ok(json!({ "deleted": deleted })),
|
|
253
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Handle vault.bundles.listSecrets
|
|
258
|
+
pub fn handle_bundles_list_secrets(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
259
|
+
let ctx = match state.to_runtime_context() {
|
|
260
|
+
Some(c) => c,
|
|
261
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
let bundle_id = match payload.get("bundleId").and_then(|v| v.as_str()) {
|
|
265
|
+
Some(id) => id,
|
|
266
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'bundleId'"),
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
let opts: Option<vault::SecretListOptions> = payload
|
|
270
|
+
.get("opts")
|
|
271
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
272
|
+
|
|
273
|
+
match vault::bundles::list_secrets(&ctx, state.vault_cache(), bundle_id, opts) {
|
|
274
|
+
Ok(secrets) => EngineResponse::ok(json!({ "secrets": secrets })),
|
|
275
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// Handle vault.bundles.addSecret
|
|
280
|
+
pub fn handle_bundles_add_secret(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
281
|
+
let ctx = match state.to_runtime_context() {
|
|
282
|
+
Some(c) => c,
|
|
283
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
let bundle_id = match payload.get("bundleId").and_then(|v| v.as_str()) {
|
|
287
|
+
Some(id) => id,
|
|
288
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'bundleId'"),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
let secret_id = match payload.get("secretId").and_then(|v| v.as_str()) {
|
|
292
|
+
Some(id) => id,
|
|
293
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'secretId'"),
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
match vault::bundles::add_secret(&ctx, state.vault_cache(), bundle_id, secret_id) {
|
|
297
|
+
Ok(bundle) => EngineResponse::ok(json!(bundle)),
|
|
298
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/// Handle vault.bundles.removeSecret
|
|
303
|
+
pub fn handle_bundles_remove_secret(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
304
|
+
let ctx = match state.to_runtime_context() {
|
|
305
|
+
Some(c) => c,
|
|
306
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
let bundle_id = match payload.get("bundleId").and_then(|v| v.as_str()) {
|
|
310
|
+
Some(id) => id,
|
|
311
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'bundleId'"),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
let secret_id = match payload.get("secretId").and_then(|v| v.as_str()) {
|
|
315
|
+
Some(id) => id,
|
|
316
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'secretId'"),
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
match vault::bundles::remove_secret(&ctx, state.vault_cache(), bundle_id, secret_id) {
|
|
320
|
+
Ok(bundle) => EngineResponse::ok(json!(bundle)),
|
|
321
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// =============================================================================
|
|
326
|
+
// Files Handlers
|
|
327
|
+
// =============================================================================
|
|
328
|
+
|
|
329
|
+
/// Handle vault.files.writeText
|
|
330
|
+
pub fn handle_files_write_text(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
331
|
+
let ctx = match state.to_runtime_context() {
|
|
332
|
+
Some(c) => c,
|
|
333
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
337
|
+
Some(p) => p,
|
|
338
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
let content = match payload.get("content").and_then(|v| v.as_str()) {
|
|
342
|
+
Some(c) => c,
|
|
343
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'content'"),
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
let opts: Option<vault::FileOptions> = payload
|
|
347
|
+
.get("opts")
|
|
348
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
349
|
+
|
|
350
|
+
match vault::files::write_text(&ctx, state.vault_cache(), path, content, opts) {
|
|
351
|
+
Ok(()) => EngineResponse::ok(json!({ "written": true })),
|
|
352
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/// Handle vault.files.writeBytes
|
|
357
|
+
pub fn handle_files_write_bytes(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
358
|
+
let ctx = match state.to_runtime_context() {
|
|
359
|
+
Some(c) => c,
|
|
360
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
364
|
+
Some(p) => p,
|
|
365
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// contentBytes is expected as base64 encoded string
|
|
369
|
+
let content_b64 = match payload.get("contentBytes").and_then(|v| v.as_str()) {
|
|
370
|
+
Some(c) => c,
|
|
371
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'contentBytes'"),
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
let content = match base64_decode(content_b64) {
|
|
375
|
+
Ok(c) => c,
|
|
376
|
+
Err(e) => return EngineResponse::err("INVALID_PAYLOAD", &format!("Invalid base64: {}", e)),
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
let opts: Option<vault::FileOptions> = payload
|
|
380
|
+
.get("opts")
|
|
381
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
382
|
+
|
|
383
|
+
match vault::files::write_bytes(&ctx, state.vault_cache(), path, &content, opts) {
|
|
384
|
+
Ok(()) => EngineResponse::ok(json!({ "written": true })),
|
|
385
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/// Handle vault.files.readText
|
|
390
|
+
pub fn handle_files_read_text(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
391
|
+
let ctx = match state.to_runtime_context() {
|
|
392
|
+
Some(c) => c,
|
|
393
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
397
|
+
Some(p) => p,
|
|
398
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
let opts: Option<vault::FileOptions> = payload
|
|
402
|
+
.get("opts")
|
|
403
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
404
|
+
|
|
405
|
+
match vault::files::read_text(&ctx, state.vault_cache(), path, opts) {
|
|
406
|
+
Ok(content) => EngineResponse::ok(json!({ "content": content })),
|
|
407
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/// Handle vault.files.readBytes
|
|
412
|
+
pub fn handle_files_read_bytes(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
413
|
+
let ctx = match state.to_runtime_context() {
|
|
414
|
+
Some(c) => c,
|
|
415
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
419
|
+
Some(p) => p,
|
|
420
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
let opts: Option<vault::FileOptions> = payload
|
|
424
|
+
.get("opts")
|
|
425
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
426
|
+
|
|
427
|
+
match vault::files::read_bytes(&ctx, state.vault_cache(), path, opts) {
|
|
428
|
+
Ok(content) => {
|
|
429
|
+
let content_b64 = base64_encode(&content);
|
|
430
|
+
EngineResponse::ok(json!({ "contentBytes": content_b64 }))
|
|
431
|
+
}
|
|
432
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/// Handle vault.files.list
|
|
437
|
+
pub fn handle_files_list(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
438
|
+
let ctx = match state.to_runtime_context() {
|
|
439
|
+
Some(c) => c,
|
|
440
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
let dir_path = payload.get("dir").and_then(|v| v.as_str()).unwrap_or("");
|
|
444
|
+
|
|
445
|
+
let opts: Option<vault::FileListOptions> = payload
|
|
446
|
+
.get("opts")
|
|
447
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
448
|
+
|
|
449
|
+
match vault::files::list(&ctx, state.vault_cache(), dir_path, opts) {
|
|
450
|
+
Ok(entries) => EngineResponse::ok(json!({ "entries": entries })),
|
|
451
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/// Handle vault.files.exists
|
|
456
|
+
pub fn handle_files_exists(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
457
|
+
let ctx = match state.to_runtime_context() {
|
|
458
|
+
Some(c) => c,
|
|
459
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
463
|
+
Some(p) => p,
|
|
464
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
let opts: Option<vault::FileOptions> = payload
|
|
468
|
+
.get("opts")
|
|
469
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
470
|
+
|
|
471
|
+
match vault::files::exists(&ctx, state.vault_cache(), path, opts) {
|
|
472
|
+
Ok(exists) => EngineResponse::ok(json!({ "exists": exists })),
|
|
473
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/// Handle vault.files.delete
|
|
478
|
+
pub fn handle_files_delete(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
479
|
+
let ctx = match state.to_runtime_context() {
|
|
480
|
+
Some(c) => c,
|
|
481
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
485
|
+
Some(p) => p,
|
|
486
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
let opts: Option<vault::FileDeleteOptions> = payload
|
|
490
|
+
.get("opts")
|
|
491
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
492
|
+
|
|
493
|
+
match vault::files::delete(&ctx, state.vault_cache(), path, opts) {
|
|
494
|
+
Ok(deleted) => EngineResponse::ok(json!({ "deleted": deleted })),
|
|
495
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/// Handle vault.files.mkdir
|
|
500
|
+
pub fn handle_files_mkdir(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
501
|
+
let ctx = match state.to_runtime_context() {
|
|
502
|
+
Some(c) => c,
|
|
503
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
let path = match payload.get("path").and_then(|v| v.as_str()) {
|
|
507
|
+
Some(p) => p,
|
|
508
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'path'"),
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
let opts: Option<vault::FileOptions> = payload
|
|
512
|
+
.get("opts")
|
|
513
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
514
|
+
|
|
515
|
+
match vault::files::mkdir(&ctx, state.vault_cache(), path, opts) {
|
|
516
|
+
Ok(()) => EngineResponse::ok(json!({ "created": true })),
|
|
517
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/// Handle vault.files.move
|
|
522
|
+
pub fn handle_files_move(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
523
|
+
let ctx = match state.to_runtime_context() {
|
|
524
|
+
Some(c) => c,
|
|
525
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
let from = match payload.get("from").and_then(|v| v.as_str()) {
|
|
529
|
+
Some(p) => p,
|
|
530
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'from'"),
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
let to = match payload.get("to").and_then(|v| v.as_str()) {
|
|
534
|
+
Some(p) => p,
|
|
535
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'to'"),
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
let opts: Option<vault::FileOptions> = payload
|
|
539
|
+
.get("opts")
|
|
540
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
541
|
+
|
|
542
|
+
match vault::files::move_file(&ctx, state.vault_cache(), from, to, opts) {
|
|
543
|
+
Ok(()) => EngineResponse::ok(json!({ "moved": true })),
|
|
544
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// =============================================================================
|
|
549
|
+
// Injection Handlers (DEFERRED - return NOT_IMPLEMENTED)
|
|
550
|
+
// =============================================================================
|
|
551
|
+
|
|
552
|
+
/// Handle vault.attachSecretsToConnector (DEFERRED)
|
|
553
|
+
pub fn handle_attach_secrets_to_connector(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
554
|
+
let ctx = match state.to_runtime_context() {
|
|
555
|
+
Some(c) => c,
|
|
556
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
let connector_id = match payload.get("connectorId").and_then(|v| v.as_str()) {
|
|
560
|
+
Some(id) => id,
|
|
561
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'connectorId'"),
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
let mappings: Vec<vault::SecretRef> = match payload.get("mappings")
|
|
565
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
|
566
|
+
{
|
|
567
|
+
Some(m) => m,
|
|
568
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing or invalid 'mappings'"),
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
match vault::attach_secrets_to_connector(&ctx, connector_id, mappings) {
|
|
572
|
+
Ok(()) => EngineResponse::ok(json!({ "attached": true })),
|
|
573
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/// Handle vault.injectSecretsIntoRun (DEFERRED)
|
|
578
|
+
/// Note: This returns success/failure only, NOT the injected values
|
|
579
|
+
pub fn handle_inject_secrets_into_run(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
580
|
+
let ctx = match state.to_runtime_context() {
|
|
581
|
+
Some(c) => c,
|
|
582
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
let run_id = match payload.get("runId").and_then(|v| v.as_str()) {
|
|
586
|
+
Some(id) => id,
|
|
587
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing 'runId'"),
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
let mappings: Vec<vault::SecretRef> = match payload.get("mappings")
|
|
591
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok())
|
|
592
|
+
{
|
|
593
|
+
Some(m) => m,
|
|
594
|
+
None => return EngineResponse::err("INVALID_PAYLOAD", "Missing or invalid 'mappings'"),
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
match vault::inject_secrets_into_run(&ctx, run_id, mappings) {
|
|
598
|
+
Ok(_env_map) => EngineResponse::ok(json!({ "injected": true })),
|
|
599
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// =============================================================================
|
|
604
|
+
// Audit Handler
|
|
605
|
+
// =============================================================================
|
|
606
|
+
|
|
607
|
+
/// Handle vault.audit.list
|
|
608
|
+
pub fn handle_audit_list(payload: &Value, state: &EngineState) -> EngineResponse {
|
|
609
|
+
let ctx = match state.to_runtime_context() {
|
|
610
|
+
Some(c) => c,
|
|
611
|
+
None => return EngineResponse::err("NOT_CONNECTED", "Engine not initialized"),
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
let opts: Option<vault::AuditListOptions> = payload
|
|
615
|
+
.get("opts")
|
|
616
|
+
.and_then(|v| serde_json::from_value(v.clone()).ok());
|
|
617
|
+
|
|
618
|
+
match vault::audit::list(&ctx, state.vault_cache(), opts) {
|
|
619
|
+
Ok(result) => EngineResponse::ok(json!(result)),
|
|
620
|
+
Err(e) => EngineResponse::err(e.code, &e.message),
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// =============================================================================
|
|
625
|
+
// Helper Functions
|
|
626
|
+
// =============================================================================
|
|
627
|
+
|
|
628
|
+
/// Simple base64 encoding
|
|
629
|
+
fn base64_encode(data: &[u8]) -> String {
|
|
630
|
+
let mut encoded = String::new();
|
|
631
|
+
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
632
|
+
|
|
633
|
+
for chunk in data.chunks(3) {
|
|
634
|
+
let mut n: u32 = 0;
|
|
635
|
+
for (i, &byte) in chunk.iter().enumerate() {
|
|
636
|
+
n |= (byte as u32) << (16 - i * 8);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
let padding = 3 - chunk.len();
|
|
640
|
+
for i in 0..4 - padding {
|
|
641
|
+
let idx = ((n >> (18 - i * 6)) & 0x3f) as usize;
|
|
642
|
+
encoded.push(ALPHABET[idx] as char);
|
|
643
|
+
}
|
|
644
|
+
for _ in 0..padding {
|
|
645
|
+
encoded.push('=');
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
encoded
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/// Simple base64 decoding
|
|
652
|
+
fn base64_decode(encoded: &str) -> Result<Vec<u8>, &'static str> {
|
|
653
|
+
fn decode_char(c: u8) -> Result<u8, &'static str> {
|
|
654
|
+
match c {
|
|
655
|
+
b'A'..=b'Z' => Ok(c - b'A'),
|
|
656
|
+
b'a'..=b'z' => Ok(c - b'a' + 26),
|
|
657
|
+
b'0'..=b'9' => Ok(c - b'0' + 52),
|
|
658
|
+
b'+' => Ok(62),
|
|
659
|
+
b'/' => Ok(63),
|
|
660
|
+
_ => Err("invalid base64 character"),
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
let input = encoded.trim_end_matches('=');
|
|
665
|
+
|
|
666
|
+
let mut output = Vec::new();
|
|
667
|
+
let mut buffer: u32 = 0;
|
|
668
|
+
let mut bits: u32 = 0;
|
|
669
|
+
|
|
670
|
+
for &byte in input.as_bytes() {
|
|
671
|
+
buffer = (buffer << 6) | decode_char(byte)? as u32;
|
|
672
|
+
bits += 6;
|
|
673
|
+
if bits >= 8 {
|
|
674
|
+
bits -= 8;
|
|
675
|
+
output.push((buffer >> bits) as u8);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
Ok(output)
|
|
680
|
+
}
|