From 3450130f178625944132d5be49551f79d5d2422f Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 26 Mar 2023 15:01:39 -0400 Subject: [PATCH] switch to scope-based tokens --- src/config.rs | 8 ++++-- src/main.rs | 1 + src/routes/v1/code_3fa.rs | 7 +++--- src/routes/v1/user_add.rs | 7 +++--- src/tokens.rs | 51 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 src/tokens.rs diff --git a/src/config.rs b/src/config.rs index 239bc2e..8022d6f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,8 +24,12 @@ pub static CONFIG: Lazy = Lazy::new(|| { #[derive(Serialize, Debug, Deserialize)] pub struct HotelConfig { pub db_uri: String, - pub authorized_3fa_tokens: Vec, pub mfa_codes_expire_in: i64, - pub authorized_new_user_tokens: Vec + pub tokens: Vec } +#[derive(Serialize, Debug, Deserialize)] +pub struct HotelConfigToken { + pub token: String, + pub scopes: Vec +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cbdee96..3ed7ccf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ pub mod models; pub mod schema; pub mod routes; pub mod util; +pub mod tokens; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); diff --git a/src/routes/v1/code_3fa.rs b/src/routes/v1/code_3fa.rs index 95e6848..bec8079 100644 --- a/src/routes/v1/code_3fa.rs +++ b/src/routes/v1/code_3fa.rs @@ -9,6 +9,7 @@ use rand::Rng; use serde::{Serialize, Deserialize}; use crate::models::{Code3FA, NewCode3FA, User}; use crate::PgPool; +use crate::tokens::{Scope, token_has_scope}; use crate::util::current_unix_time; #[derive(Serialize, Deserialize, Clone)] @@ -30,12 +31,12 @@ pub async fn get_3fa_code(pool: Data, req: Json) -> Http use crate::schema::users; use crate::schema::codes_3fa; - if !CONFIG.authorized_3fa_tokens.contains(&req.token) { + if !token_has_scope(&req.token, &Scope::Code3FAAdd) { return HttpResponse::Unauthorized().json(APIErrorResponse { errors: vec![ APIError { - code: "ERR_INVALID_CODEREQ_TOKEN".to_string(), - message: "Invalid codereq token".to_string(), + code: "ERR_MISSING_SCOPE".to_string(), + message: "This endpoint requires the 3fa:add scope".to_string(), } ], }) diff --git a/src/routes/v1/user_add.rs b/src/routes/v1/user_add.rs index ac60714..6f2e579 100644 --- a/src/routes/v1/user_add.rs +++ b/src/routes/v1/user_add.rs @@ -8,6 +8,7 @@ use crate::error::{APIError, APIErrorResponse}; use crate::models::{NewUser, User}; use crate::PgPool; use diesel::prelude::*; +use crate::tokens::{Scope, token_has_scope}; #[derive(Serialize, Deserialize, Clone)] pub struct UserAddRequest { @@ -29,12 +30,12 @@ pub struct UserResponse { pub async fn add_user_request(pool: Data, req: Json) -> HttpResponse { use crate::schema::users; - if !CONFIG.authorized_new_user_tokens.contains(&req.token) { + if !token_has_scope(&req.token, &Scope::UserAdd) { return HttpResponse::Unauthorized().json(APIErrorResponse { errors: vec![ APIError { - code: "ERR_INVALID_NEW_USER_TOKEN".to_string(), - message: "Invalid newuser token".to_string(), + code: "ERR_MISSING_SCOPE".to_string(), + message: "This endpoint requires the user:add scope".to_string(), } ], }) diff --git a/src/tokens.rs b/src/tokens.rs new file mode 100644 index 0000000..0806be8 --- /dev/null +++ b/src/tokens.rs @@ -0,0 +1,51 @@ +use crate::config::CONFIG; + +pub fn token_valid(token: &str) -> bool { + CONFIG.tokens.iter().any(|u| u.token == token) +} + +pub fn token_has_scope(token: &str, scope: &Scope) -> bool { + if !token_valid(token) { return false; } + let token = CONFIG.tokens.iter().find(|u| u.token == token).unwrap(); + let scopes: Vec = token.scopes.iter().map(|f| f.as_str().into()).collect(); + scopes.contains(scope) +} + +#[derive(Eq, PartialEq, Debug)] +pub enum Scope<'a> { + Code3FAAdd, + UserAdd, + UserRead, + UserRemove, + Unknown { scope: &'a str } +} +impl<'a> From<&str> for Scope<'a> { + fn from(value: &str) -> Self { + match value { + "3fa:add" => Self::Code3FAAdd, + "user:add" => Self::UserAdd, + "user:read" => Self::UserRead, + "user:remove" => Self::UserRemove, + _ => Self::Unknown { scope: value } + } + } +} + +impl<'a> From> for &str { + fn from(value: Scope<'a>) -> Self { + match value { + Scope::Code3FAAdd => "3fa:add", + Scope::UserAdd => "user:add", + Scope::UserRead => "user:read", + Scope::UserRemove => "user:remove", + Scope::Unknown { scope } => scope + } + } +} + +impl<'a> From> for String { + fn from(value: Scope<'a>) -> Self { + let str_val: &str = value.into(); + str_val.to_string() + } +} \ No newline at end of file