Compare commits

...

2 Commits

Author SHA1 Message Date
Jef Roosens 2cc4d53961
Moved JWT config to config file 2021-08-30 15:28:47 +02:00
Jef Roosens 3cf7661faf
Switched to yaml-based config 2021-08-30 14:27:54 +02:00
7 changed files with 118 additions and 35 deletions

35
Cargo.lock generated
View File

@ -296,6 +296,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -320,6 +326,7 @@ dependencies = [
"atomic", "atomic",
"pear", "pear",
"serde", "serde",
"serde_yaml",
"toml", "toml",
"uncased", "uncased",
"version_check", "version_check",
@ -641,6 +648,12 @@ version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.4" version = "0.4.4"
@ -1185,6 +1198,7 @@ dependencies = [
"chrono", "chrono",
"diesel", "diesel",
"diesel_migrations", "diesel_migrations",
"figment",
"hmac", "hmac",
"jwt", "jwt",
"openssl", "openssl",
@ -1270,6 +1284,18 @@ dependencies = [
"serde", "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]] [[package]]
name = "sha1" name = "sha1"
version = "0.6.0" version = "0.6.0"
@ -1738,6 +1764,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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]] [[package]]
name = "yansi" name = "yansi"
version = "0.5.0" version = "0.5.0"

View File

@ -34,6 +34,8 @@ sha2 = "*"
chrono = { version = "*", features = [ "serde" ] } chrono = { version = "*", features = [ "serde" ] }
# Encoding of refresh tokens # Encoding of refresh tokens
base64 = "0.13.0" base64 = "0.13.0"
# Reading in configuration files
figment = { version = "*", features = [ "yaml" ] }
[profile.release] [profile.release]
lto = true lto = true

23
Rb.yaml 100644
View File

@ -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"

View File

@ -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" }

View File

@ -11,6 +11,7 @@ use crate::{
db::{tokens::NewRefreshToken, users::User}, db::{tokens::NewRefreshToken, users::User},
errors::{RbError, RbResult}, errors::{RbError, RbResult},
schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users}, schema::{refresh_tokens::dsl as refresh_tokens, users::dsl as users},
RbJwtConf,
}; };
#[derive(Serialize)] #[derive(Serialize)]
@ -30,10 +31,13 @@ pub struct Claims
pub exp: i64, pub exp: i64,
} }
pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult<JWTResponse> pub fn generate_jwt_token(
conn: &PgConnection,
jwt: &RbJwtConf,
user: &User,
) -> RbResult<JWTResponse>
{ {
let secret = std::env::var("JWT_KEY").map_err(|_| RbError::Custom("Missing JWT key."))?; let key: Hmac<Sha256> = Hmac::new_from_slice(jwt.key.as_bytes())
let key: Hmac<Sha256> = Hmac::new_from_slice(secret.as_bytes())
.map_err(|_| RbError::Custom("Couldn't create Hmac key."))?; .map_err(|_| RbError::Custom("Couldn't create Hmac key."))?;
let current_time = Utc::now(); let current_time = Utc::now();
@ -43,7 +47,7 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult<JWTRespo
id: user.id, id: user.id,
username: user.username.clone(), username: user.username.clone(),
admin: user.admin, admin: user.admin,
exp: current_time.timestamp() + crate::JWT_EXP_SECONDS, exp: current_time.timestamp() + jwt.refresh_token_expire,
}; };
// Sign the claims into a new token // Sign the claims into a new token
@ -52,11 +56,11 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult<JWTRespo
.map_err(|_| RbError::Custom("Couldn't sign JWT."))?; .map_err(|_| RbError::Custom("Couldn't sign JWT."))?;
// Generate a random refresh token // Generate a random refresh token
let mut refresh_token = [0u8; crate::REFRESH_TOKEN_N_BYTES]; let mut refresh_token = vec![0u8; jwt.refresh_token_size];
thread_rng().fill(&mut refresh_token[..]); thread_rng().fill(&mut refresh_token[..]);
let refresh_expire = let refresh_expire =
(current_time + chrono::Duration::seconds(crate::REFRESH_TOKEN_EXP_SECONDS)).naive_utc(); (current_time + chrono::Duration::seconds(jwt.refresh_token_expire)).naive_utc();
// Store refresh token in database // Store refresh token in database
db::tokens::create( db::tokens::create(
@ -74,7 +78,11 @@ pub fn generate_jwt_token(conn: &PgConnection, user: &User) -> RbResult<JWTRespo
}) })
} }
pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> RbResult<JWTResponse> pub fn refresh_token(
conn: &PgConnection,
jwt: &RbJwtConf,
refresh_token: &str,
) -> RbResult<JWTResponse>
{ {
let token_bytes = let token_bytes =
base64::decode(refresh_token).map_err(|_| RbError::AuthInvalidRefreshToken)?; base64::decode(refresh_token).map_err(|_| RbError::AuthInvalidRefreshToken)?;
@ -108,5 +116,5 @@ pub fn refresh_token(conn: &PgConnection, refresh_token: &str) -> RbResult<JWTRe
.execute(conn) .execute(conn)
.map_err(|_| RbError::Custom("Couldn't update last used time."))?; .map_err(|_| RbError::Custom("Couldn't update last used time."))?;
generate_jwt_token(conn, &user) generate_jwt_token(conn, jwt, &user)
} }

View File

@ -1,11 +1,11 @@
use rocket::serde::json::Json; use rocket::{serde::json::Json, State};
use serde::Deserialize; use serde::Deserialize;
use self::{ use self::{
jwt::{generate_jwt_token, JWTResponse}, jwt::{generate_jwt_token, JWTResponse},
pass::verify_user, pass::verify_user,
}; };
use crate::{errors::RbResult, guards::User, RbDbConn}; use crate::{errors::RbResult, guards::User, RbConfig, RbDbConn, RbJwtConf};
pub mod jwt; pub mod jwt;
pub mod pass; pub mod pass;
@ -24,16 +24,24 @@ pub async fn already_logged_in(_user: User) -> String
} }
#[post("/login", data = "<credentials>", rank = 2)] #[post("/login", data = "<credentials>", rank = 2)]
pub async fn login(conn: RbDbConn, credentials: Json<Credentials>) -> RbResult<Json<JWTResponse>> pub async fn login(
conn: RbDbConn,
conf: &State<RbConfig>,
credentials: Json<Credentials>,
) -> RbResult<Json<JWTResponse>>
{ {
let credentials = credentials.into_inner(); let credentials = credentials.into_inner();
let jwt = conf.jwt.clone();
// Get the user, if credentials are valid // Get the user, if credentials are valid
let user = conn let user = conn
.run(move |c| verify_user(c, &credentials.username, &credentials.password)) .run(move |c| verify_user(c, &credentials.username, &credentials.password))
.await?; .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)] #[derive(Deserialize)]
@ -46,13 +54,15 @@ pub struct RefreshTokenRequest
#[post("/refresh", data = "<refresh_token_request>")] #[post("/refresh", data = "<refresh_token_request>")]
pub async fn refresh_token( pub async fn refresh_token(
conn: RbDbConn, conn: RbDbConn,
conf: &State<RbConfig>,
refresh_token_request: Json<RefreshTokenRequest>, refresh_token_request: Json<RefreshTokenRequest>,
) -> RbResult<Json<JWTResponse>> ) -> RbResult<Json<JWTResponse>>
{ {
let refresh_token = refresh_token_request.into_inner().refresh_token; let refresh_token = refresh_token_request.into_inner().refresh_token;
let jwt = conf.jwt.clone();
Ok(Json( 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?, .await?,
)) ))
} }

View File

@ -8,8 +8,13 @@ extern crate diesel_migrations;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
use figment::{
providers::{Env, Format, Yaml},
Figment,
};
use rocket::{fairing::AdHoc, Build, Rocket}; use rocket::{fairing::AdHoc, Build, Rocket};
use rocket_sync_db_pools::database; use rocket_sync_db_pools::database;
use serde::{Deserialize, Serialize};
mod admin; mod admin;
pub mod auth; pub mod auth;
@ -18,14 +23,6 @@ pub mod errors;
pub mod guards; pub mod guards;
pub(crate) mod schema; 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")] #[database("postgres_rb")]
pub struct RbDbConn(diesel::PgConnection); pub struct RbDbConn(diesel::PgConnection);
@ -60,16 +57,37 @@ async fn create_admin_user(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocke
Ok(rocket) 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] #[launch]
fn rocket() -> _ 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(RbDbConn::fairing())
.attach(AdHoc::try_on_ignite( .attach(AdHoc::try_on_ignite(
"Run database migrations", "Run database migrations",
run_db_migrations, run_db_migrations,
)) ))
.attach(AdHoc::try_on_ignite("Create admin user", create_admin_user)) .attach(AdHoc::try_on_ignite("Create admin user", create_admin_user))
.attach(AdHoc::config::<RbConfig>())
.mount( .mount(
"/api/auth", "/api/auth",
routes![auth::already_logged_in, auth::login, auth::refresh_token,], routes![auth::already_logged_in, auth::login, auth::refresh_token,],