diff --git a/Cargo.lock b/Cargo.lock index a670689..92643bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,6 +296,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "either" version = "1.6.1" @@ -320,6 +326,7 @@ dependencies = [ "atomic", "pear", "serde", + "serde_yaml", "toml", "uncased", "version_check", @@ -641,6 +648,12 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + [[package]] name = "lock_api" version = "0.4.4" @@ -1185,6 +1198,7 @@ dependencies = [ "chrono", "diesel", "diesel_migrations", + "figment", "hmac", "jwt", "openssl", @@ -1270,6 +1284,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039ba818c784248423789eec090aab9fb566c7b94d6ebbfa1814a9fd52c8afb2" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + [[package]] name = "sha1" version = "0.6.0" @@ -1738,6 +1764,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index ff6ac33..5b80483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ sha2 = "*" chrono = { version = "*", features = [ "serde" ] } # Encoding of refresh tokens base64 = "0.13.0" +# Reading in configuration files +figment = { version = "*", features = [ "yaml" ] } [profile.release] lto = true diff --git a/Rb.yaml b/Rb.yaml new file mode 100644 index 0000000..6b40537 --- /dev/null +++ b/Rb.yaml @@ -0,0 +1,23 @@ +default: + address: "0.0.0.0" + ports: 8000 + +debug: + keep_alive: 5 + read_timeout: 5 + write_timeout: 5 + log_level: "normal" + limits: + forms: 32768 + + admin_user: "admin" + admin_pass: "password" + jwt: + key: "secret" + refresh_token_size: 64 + # Just 5 seconds for debugging + refresh_token_expire: 5 + + databases: + postgres_rb: + url: "postgres://rb:rb@localhost:5432/rb" diff --git a/Rocket.toml b/Rocket.toml deleted file mode 100644 index e931e4d..0000000 --- a/Rocket.toml +++ /dev/null @@ -1,13 +0,0 @@ -[debug] -port = 8000 -keep_alive = 5 -read_timeout = 5 -write_timeout = 5 -log_level = "normal" -limits = { forms = 32768 } - -[debug.databases] -postgres_rb = { url = "postgres://rb:rb@localhost:5432/rb" } - -[release.databases] -postgres_rb = { url = "postgres://rb:rb@localhost:5432/rb" } diff --git a/src/auth/jwt.rs b/src/auth/jwt.rs index 83e7a1e..89ce734 100644 --- a/src/auth/jwt.rs +++ b/src/auth/jwt.rs @@ -11,6 +11,7 @@ use crate::{ db::{tokens::NewRefreshToken, users::User}, errors::{RbError, RbResult}, schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users}, + RbJwtConf, }; #[derive(Serialize)] @@ -30,10 +31,13 @@ pub struct Claims pub exp: i64, } -pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult +pub fn generate_jwt_token( + conn: &PgConnection, + jwt: &RbJwtConf, + user: &User, +) -> RbResult { - let secret = std::env::var("JWT_KEY").map_err(|_| RbError::Custom("Missing JWT key."))?; - let key: Hmac = Hmac::new_from_slice(secret.as_bytes()) + let key: Hmac = Hmac::new_from_slice(jwt.key.as_bytes()) .map_err(|_| RbError::Custom("Couldn't create Hmac key."))?; let current_time = Utc::now(); @@ -43,7 +47,7 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult RbResult RbResult RbResult +pub fn refresh_token( + conn: &PgConnection, + jwt: &RbJwtConf, + refresh_token: &str, +) -> RbResult { let token_bytes = base64::decode(refresh_token).map_err(|_| RbError::AuthInvalidRefreshToken)?; @@ -108,5 +116,5 @@ pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> RbResult String } #[post("/login", data = "", rank = 2)] -pub async fn login(conn: RbDbConn, credentials: Json) -> RbResult> +pub async fn login( + conn: RbDbConn, + conf: &State, + credentials: Json, +) -> RbResult> { let credentials = credentials.into_inner(); + let jwt = conf.jwt.clone(); // Get the user, if credentials are valid let user = conn .run(move |c| verify_user(c, &credentials.username, &credentials.password)) .await?; - Ok(Json(conn.run(move |c| generate_jwt_token(c, &user)).await?)) + Ok(Json( + conn.run(move |c| generate_jwt_token(c, &jwt, &user)) + .await?, + )) } #[derive(Deserialize)] @@ -46,13 +54,15 @@ pub struct RefreshTokenRequest #[post("/refresh", data = "")] pub async fn refresh_token( conn: RbDbConn, + conf: &State, refresh_token_request: Json, ) -> RbResult> { let refresh_token = refresh_token_request.into_inner().refresh_token; + let jwt = conf.jwt.clone(); Ok(Json( - conn.run(move |c| crate::auth::jwt::refresh_token(c, &refresh_token)) + conn.run(move |c| crate::auth::jwt::refresh_token(c, &jwt, &refresh_token)) .await?, )) } diff --git a/src/main.rs b/src/main.rs index 400c0e8..fa147e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,13 @@ extern crate diesel_migrations; #[macro_use] extern crate diesel; +use figment::{ + providers::{Env, Format, Yaml}, + Figment, +}; use rocket::{fairing::AdHoc, Build, Rocket}; use rocket_sync_db_pools::database; +use serde::{Deserialize, Serialize}; mod admin; pub mod auth; @@ -18,14 +23,6 @@ pub mod errors; pub mod guards; pub(crate) mod schema; -// Any import defaults are defined here -/// Expire time for the JWT tokens in seconds. -const JWT_EXP_SECONDS: i64 = 600; -/// Amount of bytes the refresh tokens should consist of -const REFRESH_TOKEN_N_BYTES: usize = 64; -/// Expire time for refresh tokens; here: one week -const REFRESH_TOKEN_EXP_SECONDS: i64 = 604800; - #[database("postgres_rb")] pub struct RbDbConn(diesel::PgConnection); @@ -60,16 +57,37 @@ async fn create_admin_user(rocket: Rocket) -> Result, Rocke Ok(rocket) } +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct RbJwtConf +{ + key: String, + refresh_token_size: usize, + refresh_token_expire: i64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RbConfig +{ + admin_user: String, + admin_pass: String, + jwt: RbJwtConf, +} + #[launch] fn rocket() -> _ { - rocket::build() + let figment = Figment::from(rocket::config::Config::default()) + .merge(Yaml::file("Rb.yaml").nested()) + .merge(Env::prefixed("RB_").global()); + + rocket::custom(figment) .attach(RbDbConn::fairing()) .attach(AdHoc::try_on_ignite( "Run database migrations", run_db_migrations, )) .attach(AdHoc::try_on_ignite("Create admin user", create_admin_user)) + .attach(AdHoc::config::()) .mount( "/api/auth", routes![auth::already_logged_in, auth::login, auth::refresh_token,],