switch to role based auth

This commit is contained in:
jb-alvarado 2022-06-13 18:29:37 +02:00
parent 019a163176
commit b15eb449dc
5 changed files with 45 additions and 14 deletions

View File

@ -13,16 +13,16 @@ const JWT_EXPIRATION_MINUTES: i64 = 60;
pub struct Claims { pub struct Claims {
pub id: i64, pub id: i64,
pub username: String, pub username: String,
pub permissions: Vec<String>, pub role: String,
exp: i64, exp: i64,
} }
impl Claims { impl Claims {
pub fn new(id: i64, username: String, permissions: Vec<String>) -> Self { pub fn new(id: i64, username: String, role: String) -> Self {
Self { Self {
id, id,
username, username,
permissions, role,
exp: (Utc::now() + Duration::minutes(JWT_EXPIRATION_MINUTES)).timestamp(), exp: (Utc::now() + Duration::minutes(JWT_EXPIRATION_MINUTES)).timestamp(),
} }
} }

View File

@ -128,8 +128,6 @@ pub async fn db_get_settings(id: &i64) -> Result<Settings, sqlx::Error> {
let result: Settings = sqlx::query_as(query).bind(id).fetch_one(&conn).await?; let result: Settings = sqlx::query_as(query).bind(id).fetch_one(&conn).await?;
conn.close().await; conn.close().await;
println!("{:#?}", result);
Ok(result) Ok(result)
} }

View File

@ -1,5 +1,5 @@
use actix_web::{get, http::StatusCode, patch, post, put, web, Responder}; use actix_web::{get, http::StatusCode, patch, post, put, web, Responder};
use actix_web_grants::proc_macro::has_permissions; use actix_web_grants::proc_macro::has_any_role;
use argon2::{ use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, SaltString}, password_hash::{rand_core::OsRng, PasswordHash, SaltString},
Argon2, PasswordHasher, PasswordVerifier, Argon2, PasswordHasher, PasswordVerifier,
@ -14,6 +14,7 @@ use crate::api::{
db_add_user, db_get_settings, db_login, db_role, db_update_settings, db_update_user, db_add_user, db_get_settings, db_login, db_role, db_update_settings, db_update_user,
}, },
models::{LoginUser, Settings, User}, models::{LoginUser, Settings, User},
utils::Role,
}; };
#[derive(Serialize)] #[derive(Serialize)]
@ -25,7 +26,7 @@ struct ResponseObj<T> {
/// curl -X GET http://127.0.0.1:8080/api/settings/1 -H "Authorization: Bearer <TOKEN>" /// curl -X GET http://127.0.0.1:8080/api/settings/1 -H "Authorization: Bearer <TOKEN>"
#[get("/settings/{id}")] #[get("/settings/{id}")]
#[has_permissions("admin", "user")] #[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_settings(id: web::Path<i64>) -> Result<impl Responder, ServiceError> { async fn get_settings(id: web::Path<i64>) -> Result<impl Responder, ServiceError> {
if let Ok(settings) = db_get_settings(&id).await { if let Ok(settings) = db_get_settings(&id).await {
return Ok(web::Json(ResponseObj { return Ok(web::Json(ResponseObj {
@ -43,7 +44,7 @@ async fn get_settings(id: web::Path<i64>) -> Result<impl Responder, ServiceError
/// "config_path":"/etc/ffplayout/ffplayout.yml","extra_extensions":".jpg,.jpeg,.png"}' \ /// "config_path":"/etc/ffplayout/ffplayout.yml","extra_extensions":".jpg,.jpeg,.png"}' \
/// -H "Authorization: Bearer <TOKEN>" /// -H "Authorization: Bearer <TOKEN>"
#[patch("/settings/{id}")] #[patch("/settings/{id}")]
#[has_permissions("admin")] #[has_any_role("Role::Admin", type = "Role")]
async fn patch_settings( async fn patch_settings(
id: web::Path<i64>, id: web::Path<i64>,
data: web::Json<Settings>, data: web::Json<Settings>,
@ -55,10 +56,22 @@ async fn patch_settings(
Err(ServiceError::InternalServerError) Err(ServiceError::InternalServerError)
} }
#[get("/playout/config/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_playout_config(id: web::Path<i64>) -> Result<impl Responder, ServiceError> {
if let Ok(settings) = db_get_settings(&id).await {
println!("{:?}", settings.config_path);
return Ok("settings");
};
Err(ServiceError::InternalServerError)
}
/// curl -X PUT http://localhost:8080/api/user/1 --header 'Content-Type: application/json' \ /// curl -X PUT http://localhost:8080/api/user/1 --header 'Content-Type: application/json' \
/// --data '{"email": "<EMAIL>", "password": "<PASS>"}' --header 'Authorization: <TOKEN>' /// --data '{"email": "<EMAIL>", "password": "<PASS>"}' --header 'Authorization: <TOKEN>'
#[put("/user/{id}")] #[put("/user/{id}")]
#[has_permissions("admin", "user")] #[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn update_user( async fn update_user(
id: web::Path<i64>, id: web::Path<i64>,
user: web::ReqData<LoginUser>, user: web::ReqData<LoginUser>,
@ -98,7 +111,7 @@ async fn update_user(
/// -d '{"email": "<EMAIL>", "username": "<USER>", "password": "<PASS>", "role_id": 1}' \ /// -d '{"email": "<EMAIL>", "username": "<USER>", "password": "<PASS>", "role_id": 1}' \
/// --header 'Authorization: Bearer <TOKEN>' /// --header 'Authorization: Bearer <TOKEN>'
#[post("/user/")] #[post("/user/")]
#[has_permissions("admin")] #[has_any_role("Role::Admin", type = "Role")]
async fn add_user(data: web::Json<User>) -> Result<impl Responder, ServiceError> { async fn add_user(data: web::Json<User>) -> Result<impl Responder, ServiceError> {
match db_add_user(data.into_inner()).await { match db_add_user(data.into_inner()).await {
Ok(_) => Ok("Add User Success"), Ok(_) => Ok("Add User Success"),
@ -127,7 +140,7 @@ pub async fn login(credentials: web::Json<User>) -> impl Responder {
let role = db_role(&user.role_id.unwrap_or_default()) let role = db_role(&user.role_id.unwrap_or_default())
.await .await
.unwrap_or_else(|_| "guest".to_string()); .unwrap_or_else(|_| "guest".to_string());
let claims = Claims::new(user.id, user.username.clone(), vec![role.clone()]); let claims = Claims::new(user.id, user.username.clone(), role.clone());
if let Ok(token) = create_jwt(claims) { if let Ok(token) = create_jwt(claims) {
user.token = Some(token); user.token = Some(token);

View File

@ -7,6 +7,23 @@ use crate::api::{
models::User, models::User,
}; };
#[derive(PartialEq, Clone)]
pub enum Role {
Admin,
User,
Guest,
}
impl Role {
pub fn set_role(role: &str) -> Self {
match role {
"admin" => Role::Admin,
"user" => Role::User,
_ => Role::Guest,
}
}
}
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
pub struct GlobalSettings { pub struct GlobalSettings {
pub secret: String, pub secret: String,

View File

@ -13,8 +13,8 @@ use ffplayout_engine::{
args_parse::Args, args_parse::Args,
auth, auth,
models::LoginUser, models::LoginUser,
routes::{add_user, get_settings, login, patch_settings, update_user}, routes::{add_user, get_playout_config, get_settings, login, patch_settings, update_user},
utils::{init_config, run_args}, utils::{init_config, run_args, Role},
}, },
utils::{init_logging, GlobalConfig}, utils::{init_logging, GlobalConfig},
}; };
@ -22,10 +22,12 @@ use ffplayout_engine::{
async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, Error> { async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, Error> {
// We just get permissions from JWT // We just get permissions from JWT
let claims = auth::decode_jwt(credentials.token()).await?; let claims = auth::decode_jwt(credentials.token()).await?;
req.attach(claims.permissions); req.attach(vec![Role::set_role(&claims.role)]);
req.extensions_mut() req.extensions_mut()
.insert(LoginUser::new(claims.id, claims.username)); .insert(LoginUser::new(claims.id, claims.username));
println!("{:#?}", req);
Ok(req) Ok(req)
} }
@ -63,6 +65,7 @@ async fn main() -> std::io::Result<()> {
web::scope("/api") web::scope("/api")
.wrap(auth) .wrap(auth)
.service(add_user) .service(add_user)
.service(get_playout_config)
.service(get_settings) .service(get_settings)
.service(patch_settings) .service(patch_settings)
.service(update_user), .service(update_user),