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}