switch to scope-based tokens
This commit is contained in:
parent
b188b399bb
commit
3450130f17
|
@ -24,8 +24,12 @@ pub static CONFIG: Lazy<HotelConfig> = Lazy::new(|| {
|
||||||
#[derive(Serialize, Debug, Deserialize)]
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
pub struct HotelConfig {
|
pub struct HotelConfig {
|
||||||
pub db_uri: String,
|
pub db_uri: String,
|
||||||
pub authorized_3fa_tokens: Vec<String>,
|
|
||||||
pub mfa_codes_expire_in: i64,
|
pub mfa_codes_expire_in: i64,
|
||||||
pub authorized_new_user_tokens: Vec<String>
|
pub tokens: Vec<HotelConfigToken>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
pub struct HotelConfigToken {
|
||||||
|
pub token: String,
|
||||||
|
pub scopes: Vec<String>
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ pub mod models;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
pub mod tokens;
|
||||||
|
|
||||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use rand::Rng;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::models::{Code3FA, NewCode3FA, User};
|
use crate::models::{Code3FA, NewCode3FA, User};
|
||||||
use crate::PgPool;
|
use crate::PgPool;
|
||||||
|
use crate::tokens::{Scope, token_has_scope};
|
||||||
use crate::util::current_unix_time;
|
use crate::util::current_unix_time;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
@ -30,12 +31,12 @@ pub async fn get_3fa_code(pool: Data<PgPool>, req: Json<CodeRequest3FA>) -> Http
|
||||||
use crate::schema::users;
|
use crate::schema::users;
|
||||||
use crate::schema::codes_3fa;
|
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 {
|
return HttpResponse::Unauthorized().json(APIErrorResponse {
|
||||||
errors: vec![
|
errors: vec![
|
||||||
APIError {
|
APIError {
|
||||||
code: "ERR_INVALID_CODEREQ_TOKEN".to_string(),
|
code: "ERR_MISSING_SCOPE".to_string(),
|
||||||
message: "Invalid codereq token".to_string(),
|
message: "This endpoint requires the 3fa:add scope".to_string(),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::error::{APIError, APIErrorResponse};
|
||||||
use crate::models::{NewUser, User};
|
use crate::models::{NewUser, User};
|
||||||
use crate::PgPool;
|
use crate::PgPool;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use crate::tokens::{Scope, token_has_scope};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct UserAddRequest {
|
pub struct UserAddRequest {
|
||||||
|
@ -29,12 +30,12 @@ pub struct UserResponse {
|
||||||
pub async fn add_user_request(pool: Data<PgPool>, req: Json<UserAddRequest>) -> HttpResponse {
|
pub async fn add_user_request(pool: Data<PgPool>, req: Json<UserAddRequest>) -> HttpResponse {
|
||||||
use crate::schema::users;
|
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 {
|
return HttpResponse::Unauthorized().json(APIErrorResponse {
|
||||||
errors: vec![
|
errors: vec![
|
||||||
APIError {
|
APIError {
|
||||||
code: "ERR_INVALID_NEW_USER_TOKEN".to_string(),
|
code: "ERR_MISSING_SCOPE".to_string(),
|
||||||
message: "Invalid newuser token".to_string(),
|
message: "This endpoint requires the user:add scope".to_string(),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -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<Scope> = 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<Scope<'a>> 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<Scope<'a>> for String {
|
||||||
|
fn from(value: Scope<'a>) -> Self {
|
||||||
|
let str_val: &str = value.into();
|
||||||
|
str_val.to_string()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue