Build a password manager with Rust - Part 1
Tags: rust, learning
Published: 2024-05-21 17:59:54


## Introduction

To learn a new language, I find it quite interesting to build something. Having simple features at first to get the hang of the language, then adding more stuff to it, making it more complex, faster, cleaner...

So this is what we'll try to do together in this series. We will build a password manager in Rust. The first article will start with simple features. By the end, we'll have a command line application with:
- a way to display our passwords
- a way to save a new password 

No encryption, password generations, user interfaces... These will come later. Ok, let's go!

## First step: The command line

For this project, we will use the Clap crate to interact easily with the command line. We already used it in [this article]
(https://damiencosset.dev/posts/create-command-line-app-rust).

So, let's recap what we want from our command line arguments:
- an argument to list our existing passwords
- an argument to save a new password


Here's how we can do it:

```rust
use clap::{Parser, Subcommand};

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    cmd: Commands,
}

#[derive(Subcommand)]
enum Commands {
    List,
    Add {
        service: String,
        username: String,
        password: String,
    },
}
```

What's going on here? Using Clap, we define a *Cli* structure with a *cmd*. This can take two values, List or Add (in our enum *Commands*). Note that the #[derive(...)] syntax tells Rust that we want our enum or struc to implement certains traits. In our case, we want our enum *Commands* to implement the *Subcommand* trait from Clap.

Our List subcommand doesn't take any arguments. But our Add subcommand will take three, the name of the service, a username and a password.

Great! Let's add some code in our main function to see how we interact with all of this:

```rust
use clap::{Parser, Subcommand};

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    cmd: Commands,
}

#[derive(Subcommand)]
enum Commands {
    List,
    Add {
        service: String,
        username: String,
        password: String,
    },
}

fn main() -> std::io::Result<()> {
    let args = Cli::parse();
    match args.cmd {
        Commands::List => display_passwords(),
        Commands::Add {
            service,
            username,
            password,
        } => add_new_password(service, username, password),
    }
    Ok(())
}

fn display_passwords() {
    println!("Will display passwords")
}

fn add_new_password(service: String, username: String, password: String) {
    println!("Will add new password.");
    println!("Service:{}", service);
    println!("Username:{}", username);
    println!("Password:{}", password);
}
```

We add a *match* in the main function to handle the different commands we can handle. We add two functions that will only print out something for now. Let's run it!

Cargo run list:
![Cargo run list displaying our text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7k4w2os06r4kasd9mwcx.png)

Cargo run add command:
![Cargo run add with three arguments displaying all three arguments properly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zy7axygdto06u834ml1p.png)

What happens if we run the add command without the correct number of arguments?

![Cargo run add without all arguments displaying an error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gzecj0yewp2w4kfhi3ln.png)

It's another nice thing about Clap, it gives us helpful information about the arguments we can use. It also gives us the *help* command by default:

![Cargo run help displaying the informations about the command line application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rluqz2jl6cg51xn2212y.png)


## Step 2: Having a way to store our passwords

So, this works great, but our functions don't do anything yet. We need to decide how we are going to store our passwords. For now, we are simply going to have a .txt file. So, at the root of our project, we are going to add a *passwords.txt* file. We'll define some rules for our very simple implementation:
- each password will have its own line in the file
- on each line, we'll separate the service, username and password with a special character

This is obviously far from being perfect, but it will do the job in our quest to learn the language. Let's do it!

Our passwords.txt will have some entries already for testing purposes:

```
dev.to|Damien|my dev password
twitter|Bobby|123Awesome456

```

Our List command will read from this file and display its contents. Let's update our *display_passwords* function:

```rust
//All our imports
use clap::{Parser, Subcommand};
use std::{
    fs::{self},
    path::Path,
};

// Rest of the code ...

fn display_passwords() {
    let path = Path::new("./passwords.txt");
    let contents = fs::read_to_string(path).expect("Could not read the passwords file");

    println!("{}", contents)
}
```

We create a new *Path* with our relative file path and we pass it to the *read_to_string* function provided by the <a href="https://doc.rust-lang.org/std/fs/index.html" target="_blank">fs module</a>. 
*Note*: If you put the txt file in the src folder, your relative path will be *./src/passwords.txt*.

The *.expect()* will print out the message we provided if we encounter an error. Let's check the results:

![Cargo run list with the passwords.txt file contents displayed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97ol5r202v62c1w8i4qv.png)

Nice! On to the next step.

## Step 3: Adding a new password to our file

We're almost there! Now, we need to implement our *add_new_password* function.

Step 4: 
- Writing to the new file

```rust
fn add_new_password(service: String, username: String, password: String) -> std::io::Result<()> {
    let path = Path::new("./passwords.txt");
    let password_infos = format!("{}|{}|{}\n", service, username, password);

    let mut file = OpenOptions::new().append(true).open(path)?;

    file.write_all(password_infos.as_bytes())?;

    Ok(())
}
```

After creating our *Path*, we use *format!* to format our String we want to append to our passwords.txt file. Then, we use *OpenOptions*. Provided by the fs module, it defines how we want to open a file. In our case, we want to append data to the contents, so we use *append(true)*. Notice that we use the *mut* keyword, to tell Rust that this variable is mutable.

Then, we use the *write_all* function to append our password informations. Careful, the *write_all* function expects a *u8* primitive type for its argument. That's why we use *as_bytes()*.

Finally, we return Ok(()).

And we should be done! Our full code looks like this:

```rust
use clap::{Parser, Subcommand};
use std::io::Write;
use std::{
    fs::{self, OpenOptions},
    path::Path,
};

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    cmd: Commands,
}

#[derive(Subcommand)]
enum Commands {
    List,
    Add {
        service: String,
        username: String,
        password: String,
    },
}

fn main() -> std::io::Result<()> {
    let args = Cli::parse();
    match args.cmd {
        Commands::List => display_passwords(),
        Commands::Add {
            service,
            username,
            password,
        } => add_new_password(service, username, password)?,
    }
    Ok(())
}

fn display_passwords() {
    let path = Path::new(".passwords.txt");
    let contents = fs::read_to_string(path).expect("Could not read the passwords file");

    println!("{}", contents);
}

fn add_new_password(service: String, username: String, password: String) -> std::io::Result<()> {
    let path = Path::new("./passwords.txt");
    let password_infos = format!("{}|{}|{}\n", service, username, password);

    let mut file = OpenOptions::new().append(true).open(path)?;

    file.write_all(password_infos.as_bytes())?;

    Ok(())
}
```

Time to run this and test it out!

Adding a new password:

![Cargo run add with three arguments, with the username having a space in the middle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9jtyrj5z49qpcurlnb0.png)

Listing our passwords list:

![Cargo run list with our new password we just created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gxwseq7eis8irup0g05y.png)

Yay! It works! Congratulations!

In this article, we explored a bit deeper the Clap crate to create subcommands. Then, we learned how to interact with files thanks to the fs module. In the next articles, we'll keep making this little project better. You can find the code [here](https://github.com/Tagada85/rust_password_manager)

Have fun :heart: