Storing Data
Because having an intern enter secrets into a spreadsheet would mean I'd have to write a job description.
Let's talk about project structure for a second. Thruster doesn't prescribe any sort of methodology for how you should organize your code, but I'll sprinkle in how I tend to organize mine. Feel free to ignore this bit in italics. I usually choose one of two ways: 1. Domain driven, where a folder contains the routes, models, and maybe some associated services or helpers. For example, a users folder would have all of the routes for getting, creating, updating a user, as well as the user model and methods for creating a user in the database.
2. MVC (or rather, MCS perhaps?) I'll setup a folder for models, controllers (or routes,) and services. Models are simple structs and methods for handling them in the storage layer. Controllers (or routes) are the code for taking an external request and making service calls and model updates. Services is a catchall for everything else; 3rd party calls, external services, and utility methods.
This project will use method 2, because frankly it's a little easier to grok for small projects, however 1. is nice for larger projects due to the isolation of each domain.
Let's start by creating a few folders in src
. Make a routes
folder and a models
folder, their contents should be fairly self explanatory, but in case you wanted assurance, route handling will go in routes
and database models will go in models
.
A great place to start is with the model to store the data. So create a file named secret.rs
in models
. Because of how rust modules work, we'll also need to add models module to lib.rs
and main.rs
-- add the following line to both:
We'll also need a mod.rs
file in the models/
folder, so throw that in there with the following line:
Now for the good stuff. In secret.rs
, add this:
We'll be using AES encryption to store the secrets. The implementation of which is FAR beyond the scope of this tutorial, just know that, for our purposes, password + secret -> encoded text.
In order to make guessing passwords a bit harder, we'll be using Argon2id. This means that if the database were to be compromised, it would be harder to brute force the secrets because it takes a long (comparatively) time to run Argon2id.
This is the struct representation of the data in our database. It's important to note here that we derive
FromRow
from thesqlx
package. I also like to addDebug
to my structs so they're easier to, well, debug!In order to handle errors in a more concise way, i.e. not using a
Box<dyn Error>
, we'll set up an enum with a few values that can wrap the errors we might occur while we're inserting and later fetching data from the database.We generate a salt here to use in our hashing.
The password hash is generated from the salt and the password, which we refer to as "code."
The password hash is used as one of the inputs for the AES algorithm along with the secret.
Finally we take the cipher text output from the AES algorithm, base64 encode it, and insert it into our database. We also return the full
Secret
object to the caller.
You might get some errors at this point -- some of these libraries aren't included in our Cargo.toml
yet. Update your [dependencies]
to look like the following:
We now need to add the route so that we can take the incoming request and call the function we just created. First, create a new folder, src/controllers
. Then add two files in that folder, mod.rs
and secrets.rs
. We need to add that module to both main.rs
and lib.rs
by adding the line pub mod controllers;
to each.
We also need to add the secrets module to the controllers/mod.rs
-- pub mod secrets;
. Now we can add the route! Make your secrets controller look like this:
First we create the incoming request struct. I like to add both the
Serialize
andDeserialize
trait regardless of which direction the middleware will actually use the struct. This way we can use the same struct to generate requests in our testing!Use the
json
method to parse the body of the context intojson
. Because bodies might be large amounts of streaming data, this is an asynchronous function, so we await it.In order to make a password, we generate a short random string. This will be used along with the ID to decode and fetch the secret respectively.
We use the function we just created to insert the secret into our database.
Finally, we call the
json
method on the context, which will serialize the passed in object and set the body as such. This will also set theContent-Type
header to beapplication/json
.
We're almost done! We just need to actually add the route to our app router. In order to do this, open up src/app.rs
and the following line:
Note that you'll have to add the create_secret
import. I trust you, you can do this.
Now if you run the code (make sure your database is running!) using cargo run
, you should be able to make a curl to insert a code into the database!
Assuming you got a code, you're all good! Keep on going when you're ready to do some fetchin'!
Last updated