chamber_cli/
commands.rs

1use chamber_core::core::AuthBody;
2use chamber_core::secrets::SecretInfo;
3use comfy_table::Table;
4use inquire::Text;
5use reqwest::StatusCode;
6
7use crate::errors::CliError;
8
9use crate::args::{Cli, Commands, SecretsCommands, UserCommands, WebsiteCommands};
10
11use crate::config::AppConfig;
12use chamber_core::secrets::KeyFile;
13
14pub fn parse_cli(cli: Cli, cfg: AppConfig) -> Result<(), CliError> {
15    match cli.command {
16        Commands::Secrets { cmd } => match cmd {
17            SecretsCommands::Get(args) => {
18                let Some(jwt) = cfg.clone().jwt_key() else {
19                    panic!("You need to log in before you can do that!");
20                };
21
22                let website = match cfg.website() {
23                    Some(res) => format!("{res}/secrets/get"),
24                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
25                };
26
27                let key = match args.key {
28                    Some(res) => res,
29                    None => Text::new("Please enter the key you want to retrieve:").prompt()?,
30                };
31
32                let ctx = reqwest::blocking::Client::new();
33
34                let res = ctx
35                    .post(website)
36                    .header("Content-Type", "application/json")
37                    .header("Authorization", jwt)
38                    .json(&serde_json::json!({"key":key}))
39                    .send()?;
40
41                let body = res.text()?;
42
43                println!("{body}");
44            }
45
46            SecretsCommands::Set { key, value } => {
47                let Some(jwt) = cfg.clone().jwt_key() else {
48                    panic!("You need to log in before you can do that!");
49                };
50
51                let website = match cfg.website() {
52                    Some(res) => format!("{res}/secrets/set"),
53                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
54                };
55
56                let ctx = reqwest::blocking::Client::new();
57
58                let res = ctx
59                    .post(website)
60                    .header("Content-Type", "application/json")
61                    .header("Authorization", jwt)
62                    .json(&serde_json::json!({"key":key,"value":value}))
63                    .send()?;
64
65                match res.status() {
66                    StatusCode::CREATED => println!("Key successfully set."),
67                    _ => {
68                        println!("Bad credentials: {}", res.status())
69                    }
70                }
71            }
72            SecretsCommands::Update { key, tags } => {
73                let Some(jwt) = cfg.clone().jwt_key() else {
74                    panic!("You need to log in before you can do that!");
75                };
76
77                let website = match cfg.website() {
78                    Some(res) => format!("{res}/secrets"),
79                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
80                };
81
82                let ctx = reqwest::blocking::Client::new();
83
84                let res = ctx
85                    .put(website)
86                    .header("Authorization", jwt)
87                    .json(&serde_json::json!({
88                        "key": key,
89                        "update_data": tags
90                    }))
91                    .send()?;
92
93                match res.status() {
94                    StatusCode::OK => println!("Meme"),
95                    _ => println!("Not OK!"),
96                }
97            }
98            SecretsCommands::List(args) => {
99                let Some(jwt) = cfg.clone().jwt_key() else {
100                    panic!("You need to log in before you can do that!");
101                };
102
103                let website = match cfg.website() {
104                    Some(res) => format!("{res}/secrets"),
105                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
106                };
107
108                let ctx = reqwest::blocking::Client::new();
109
110                let res = ctx
111                    .post(website)
112                    .header("Authorization", jwt)
113                    .json(&serde_json::json!({
114                        "tag_filter": args.tags
115                    }))
116                    .send()?;
117
118                let json = res.json::<Vec<SecretInfo>>().unwrap();
119
120                let table = secrets_table(json);
121
122                println!("{table}");
123            }
124            SecretsCommands::Rm(args) => {
125                let Some(jwt) = cfg.clone().jwt_key() else {
126                    panic!("You need to log in before you can do that!");
127                };
128
129                let website = match cfg.website() {
130                    Some(res) => format!("{res}/secrets"),
131                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
132                };
133
134                let key = match args.key {
135                    Some(res) => res,
136                    None => Text::new("Please enter the key you want to retrieve:").prompt()?,
137                };
138
139                let ctx = reqwest::blocking::Client::new();
140
141                let res = ctx
142                    .delete(website)
143                    .header("Authorization", jwt)
144                    .json(&serde_json::json!({"key":key}))
145                    .send()?;
146
147                match res.status() {
148                    StatusCode::OK => println!("Key successfully deleted."),
149                    _ => println!("Error while deleting key: {}", res.text().unwrap()),
150                }
151            }
152        },
153        Commands::Keygen(args) => {
154            let key = match args.key {
155                Some(res) => KeyFile::from_key(&res),
156                None => KeyFile::new(),
157            };
158
159            let encoded = bincode::serialize(&key).unwrap();
160
161            std::fs::write("chamber.bin", encoded).unwrap();
162
163            println!("Your root key: {}", key.unseal_key());
164            println!(
165                "Be sure to keep this file somewhere safe - you won't be able to get it back!"
166            );
167            println!("---");
168        }
169
170        Commands::Users { cmd } => match cmd {
171            UserCommands::Create(args) => {
172                let website = match cfg.website() {
173                    Some(res) => format!("{res}/users/create"),
174                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
175                };
176
177                let key = Text::new("Please enter your root key:").prompt()?;
178
179            let username = match args.username {
180                Some(res) => res,
181                None => Text::new("Please enter your desired username:").prompt()?,
182            };
183            let password = match args.password {
184                Some(res) => res,
185                None => Text::new("Please enter your desired password:").prompt()?,
186            };
187                let ctx = reqwest::blocking::Client::new();
188
189                let res = ctx
190                    .post(website)
191                    .header("Content-Type", "application/json")
192                    .header("x-chamber-key", key)
193                    .json(&serde_json::json!({"username": username, "password": password}))
194                    .send()?;
195
196                match res.status() {
197                    StatusCode::CREATED => {
198                        println!("User created! Make sure you keep the credentials somewhere safe.!");
199                    }
200                    _ => {
201                        println!("Error: {}", res.text()?)
202                    }
203                }
204            }
205            UserCommands::Update(args) => {
206                let website = match cfg.website() {
207                    Some(res) => format!("{res}/users/create"),
208                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
209                };
210
211                if args.access_level.is_none() & args.roles.is_none() {
212                    return Err(CliError::AtLeastOneArgError);
213                } 
214
215                let key = Text::new("Please enter your root key:").prompt()?;
216
217                let ctx = reqwest::blocking::Client::new();
218
219                let res = ctx
220                    .post(website)
221                    .header("Content-Type", "application/json")
222                    .header("x-chamber-key", key)
223                    .json(&serde_json::json!({
224                        "username": args.username, 
225                        "access_level": args.access_level,
226                        "roles": args.roles
227                        }))
228                    .send()?;
229
230                match res.status() {
231                    StatusCode::OK => {
232                        println!("User has been updated.");
233                    }
234                    _ => {
235                        println!("Error: {}", res.text()?)
236                    }
237                }
238            }
239
240            UserCommands::Delete(args) => {
241                let website = match cfg.website() {
242                    Some(res) => format!("{res}/users/delete"),
243                    None => panic!("You didn't set a URL for a Chamber instance to log into!"),
244                };
245
246                let key = Text::new("Please enter your root key:").prompt()?;
247
248            let username = match args.username {
249                Some(res) => res,
250                None => Text::new("Name of the user to be deleted:").prompt()?,
251            };
252
253                let ctx = reqwest::blocking::Client::new();
254
255                let res = ctx
256                    .post(website)
257                    .header("Content-Type", "application/json")
258                    .header("x-chamber-key", key)
259                    .json(&serde_json::json!({
260                        "username": username
261                    }))
262                    .send()?;
263
264                match res.status() {
265                    StatusCode::OK => {
266                        println!("User has been deleted.");
267                    }
268                    _ => {
269                        println!("Error: {}", res.text()?)
270                    }
271                }
272            }
273        },
274        Commands::Website { cmd } => match cmd {
275            WebsiteCommands::Get => match cfg.website() {
276                Some(res) => println!("{res}"),
277                None => println!("No website has been set!"),
278            },
279            WebsiteCommands::Set(args) => {
280                let value = match args.value {
281                Some(res) => res,
282                None => Text::new("Enter the website URL:").prompt()?,
283                };
284                cfg.set_website(&value)?;
285            }
286        },
287
288        Commands::Login(args) => {
289            let username = match args.username {
290                Some(res) => res,
291                None => Text::new("Please enter your username:").prompt()?,
292            };
293            let password = match args.password {
294                Some(res) => res,
295                None => Text::new("Please enter your password:").prompt()?,
296            };
297
298            let ctx = reqwest::blocking::Client::new();
299
300            let website = match cfg.to_owned().website() {
301                Some(res) => format!("{res}/login"),
302                None => panic!("You didn't set a URL for a Chamber instance to log into!"),
303            };
304
305            let res = ctx
306                .post(website)
307                .header("Content-Type", "application/json")
308                .json(&serde_json::json!({
309                    "username": username,
310                    "password": password 
311                }))
312                .send()?;
313        match res.status() {
314            StatusCode::OK => {
315            let res = res.json::<AuthBody>()?;
316
317            let token = format!("{} {}", res.token_type, res.access_token);
318            cfg.set_token(&token)?;
319
320            println!("You've logged in successfully!");
321            },
322            _ => {
323            println!("Something went wrong: {}", res.text()?);
324            },
325
326        }
327
328        }
329
330        Commands::Unseal { chamber_key } => {
331            let ctx = reqwest::blocking::Client::new();
332
333            let website = match cfg.to_owned().website() {
334                Some(res) => format!("{res}/unseal"),
335                None => panic!("You didn't set a URL for a Chamber instance to log into!"),
336            };
337
338            let res = ctx
339                .post(website)
340                .header("Content-Type", "application/json")
341                .header("x-chamber-key", chamber_key)
342                .send()?;
343
344            match res.status() {
345                StatusCode::OK => println!("The instance has been unsealed and is ready to use!"),
346                _ => {
347                    println!("{}", res.text()?);
348                }
349            }
350        }
351        Commands::Upload(args) => {
352            let key = match args.key {
353                Some(res) => res,
354                None => Text::new("Please enter your root key:").prompt()?,
355            };
356            let ctx = reqwest::blocking::Client::new();
357
358            let website = match cfg.to_owned().website() {
359                Some(res) => format!("{res}/binfile"),
360                None => panic!("You didn't set a URL for a Chamber instance to log into!"),
361            };
362
363            let file = std::fs::read("chamber.bin")?;
364
365            let form = reqwest::blocking::multipart::Form::new();
366            let file_as_bytes = reqwest::blocking::multipart::Part::bytes(file);
367
368            let form = form.part("file", file_as_bytes);
369
370            let res = ctx
371                .post(website)
372                .header("x-chamber-key", key)
373                .multipart(form)
374                .send()?;
375
376            match res.status() {
377                StatusCode::OK => {
378                    println!("The new crypto key and root key have been uploaded!");
379                    println!(
380                        "Note that any previous secrets you stored will need to be re-uploaded."
381                    );
382                }
383                _ => {
384                    println!("{}", res.text()?);
385                }
386            }
387        }
388    }
389
390    Ok(())
391}
392
393pub fn secrets_table(secrets: Vec<SecretInfo>) -> Table {
394    let mut table = Table::new();
395    table.set_header(vec!["Secret Key", "Tags"]);
396
397    secrets.into_iter().for_each(|x| {
398        table.add_row(vec![x.key, x.tags.join(", ")]);
399    });
400
401    table
402}