bw/
main.rs

1#![doc = include_str!("../README.md")]
2
3use base64::{Engine, engine::general_purpose::STANDARD};
4use bitwarden_cli::install_color_eyre;
5use clap::{CommandFactory, Parser};
6use clap_complete::Shell;
7use color_eyre::eyre::Result;
8use tracing_subscriber::{
9    EnvFilter, prelude::__tracing_subscriber_SubscriberExt as _, util::SubscriberInitExt as _,
10};
11
12use crate::{command::*, render::CommandResult};
13
14mod admin_console;
15mod auth;
16mod command;
17mod key_management;
18mod platform;
19mod render;
20mod tools;
21mod vault;
22
23#[tokio::main(flavor = "current_thread")]
24async fn main() -> Result<()> {
25    // the log level hierarchy is determined by:
26    //    - if RUST_LOG is detected at runtime
27    //    - if RUST_LOG is provided at compile time
28    //    - default to INFO
29    let filter = EnvFilter::builder()
30        .with_default_directive(
31            option_env!("RUST_LOG")
32                .unwrap_or("info")
33                .parse()
34                .expect("should provide valid log level at compile time."),
35        )
36        // parse directives from the RUST_LOG environment variable,
37        // overriding the default directive for matching targets.
38        .from_env_lossy();
39
40    tracing_subscriber::registry()
41        .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
42        .with(filter)
43        .init();
44
45    let cli = Cli::parse();
46    install_color_eyre(cli.color)?;
47    let render_config = render::RenderConfig::new(&cli);
48
49    let Some(command) = cli.command else {
50        let mut cmd = Cli::command();
51        cmd.print_help()?;
52        return Ok(());
53    };
54
55    let result = process_commands(command, cli.session).await;
56
57    // Render the result of the command
58    render_config.render_result(result)
59}
60
61async fn process_commands(command: Commands, _session: Option<String>) -> CommandResult {
62    // Try to initialize the client with the session if provided
63    // Ideally we'd have separate clients and this would be an enum, something like:
64    // enum CliClient {
65    //   Unlocked(_),  // If the user already logged in and the provided session is valid
66    //   Locked(_),    // If the user is logged in, but the session hasn't been provided
67    //   LoggedOut(_), // If the user is not logged in
68    // }
69    // If the session was invalid, we'd just return an error immediately
70    // This would allow each command to match on the client type that they need, and we don't need
71    // to do two matches over the whole command tree
72    let client = bitwarden_pm::PasswordManagerClient::new(None);
73
74    match command {
75        // Auth commands
76        Commands::Login(args) => args.run().await,
77        Commands::Logout => todo!(),
78
79        // KM commands
80        Commands::Lock => todo!(),
81        Commands::Unlock(_args) => todo!(),
82
83        // Platform commands
84        Commands::Sync { .. } => todo!(),
85
86        Commands::Encode => {
87            let input = std::io::read_to_string(std::io::stdin())?;
88            let encoded = STANDARD.encode(input);
89            Ok(encoded.into())
90        }
91
92        Commands::Config { command } => command.run().await,
93
94        Commands::Update { .. } => todo!(),
95
96        Commands::Completion { shell } => {
97            let Some(shell) = shell.or_else(Shell::from_env) else {
98                return Ok(
99                    "Couldn't autodetect a valid shell. Run `bw completion --help` for more info."
100                        .into(),
101                );
102            };
103
104            let mut cmd = Cli::command();
105            let name = cmd.get_name().to_string();
106            clap_complete::generate(shell, &mut cmd, name, &mut std::io::stdout());
107            Ok(().into())
108        }
109
110        Commands::Status => todo!(),
111
112        // Vault commands
113        Commands::List(_args) => todo!(),
114        Commands::Get { command } => match command {
115            GetCommands::Template { command } => command.run(),
116            _ => todo!("Get command implementation with {:?}", command),
117        },
118        Commands::Create { .. } => todo!(),
119        Commands::Edit(_args) => todo!(),
120        Commands::Delete { .. } => todo!(),
121        Commands::Restore(_args) => todo!(),
122
123        // Admin console commands
124        Commands::Confirm { .. } => todo!(),
125        Commands::DeviceApproval => todo!(),
126        Commands::Move(_args) => todo!(),
127
128        // Tools commands
129        Commands::Generate(arg) => arg.run(&client),
130        Commands::Import(_args) => todo!(),
131        Commands::Export(_args) => todo!(),
132        Commands::Send(_args) => todo!(),
133        Commands::Receive(_args) => todo!(),
134
135        // Server commands
136        Commands::Serve(_args) => todo!(),
137    }
138}