Skip to content

Commit

Permalink
add db trait, move config to state, fix flist_exists, get_user and vi…
Browse files Browse the repository at this point in the history
…sit_dir_one_level functions
  • Loading branch information
rawdaGastan committed Sep 18, 2024
1 parent e588b80 commit 2efda5e
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 99 deletions.
42 changes: 22 additions & 20 deletions fl-server/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::sync::Arc;

use axum::{
extract::{Json, Request, State},
http::{self, StatusCode},
middleware::Next,
response::IntoResponse,
Extension,
};
use axum_macros::debug_handler;
use chrono::{Duration, Utc};
Expand All @@ -16,12 +17,6 @@ use crate::{
response::{ResponseError, ResponseResult},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub username: String,
pub password: String,
}

#[derive(Serialize, Deserialize)]
pub struct Claims {
pub exp: usize, // Expiry time of the token
Expand Down Expand Up @@ -52,10 +47,10 @@ pub struct SignInResponse {
)]
#[debug_handler]
pub async fn sign_in_handler(
Extension(cfg): Extension<config::Config>,
State(state): State<Arc<config::AppState>>,
Json(user_data): Json<SignInBody>,
) -> impl IntoResponse {
let user = match get_user_by_username(cfg.users, &user_data.username) {
let user = match state.db.get_user_by_username(&user_data.username) {
Some(user) => user,
None => {
return Err(ResponseError::Unauthorized(
Expand All @@ -70,19 +65,18 @@ pub async fn sign_in_handler(
));
}

let token = encode_jwt(user.username, cfg.jwt_secret, cfg.jwt_expire_hours)
.map_err(|_| ResponseError::InternalServerError)?;
let token = encode_jwt(
user.username.clone(),
state.config.jwt_secret.clone(),
state.config.jwt_expire_hours,
)
.map_err(|_| ResponseError::InternalServerError)?;

Ok(ResponseResult::SignedIn(SignInResponse {
access_token: token,
}))
}

fn get_user_by_username(users: Vec<User>, username: &str) -> Option<User> {
let user = users.iter().find(|u| u.username == username)?;
Some(user.clone())
}

pub fn encode_jwt(
username: String,
jwt_secret: String,
Expand Down Expand Up @@ -112,7 +106,7 @@ pub fn decode_jwt(jwt_token: String, jwt_secret: String) -> Result<TokenData<Cla
}

pub async fn authorize(
State(cfg): State<config::Config>,
State(state): State<Arc<config::AppState>>,
mut req: Request,
next: Next,
) -> impl IntoResponse {
Expand All @@ -129,7 +123,15 @@ pub async fn authorize(

let mut header = auth_header.split_whitespace();
let (_, token) = (header.next(), header.next());
let token_data = match decode_jwt(token.unwrap().to_string(), cfg.jwt_secret) {
let token_str = match token {
Some(t) => t.to_string(),
None => {
log::error!("failed to get token string");
return Err(ResponseError::InternalServerError);
}
};

let token_data = match decode_jwt(token_str, state.config.jwt_secret.clone()) {
Ok(data) => data,
Err(_) => {
return Err(ResponseError::Forbidden(
Expand All @@ -138,7 +140,7 @@ pub async fn authorize(
}
};

let current_user = match get_user_by_username(cfg.users, &token_data.claims.username) {
let current_user = match state.db.get_user_by_username(&token_data.claims.username) {
Some(user) => user,
None => {
return Err(ResponseError::Unauthorized(
Expand All @@ -147,6 +149,6 @@ pub async fn authorize(
}
};

req.extensions_mut().insert(current_user.username);
req.extensions_mut().insert(current_user.username.clone());
Ok(next.run(req).await)
}
18 changes: 13 additions & 5 deletions fl-server/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, sync::Mutex};
use std::{
collections::HashMap,
fs,
sync::{Arc, Mutex},
};
use utoipa::ToSchema;

use crate::{auth, handlers};
use crate::{
db::{User, DB},
handlers,
};

#[derive(Debug, ToSchema, Serialize, Clone)]
pub struct Job {
pub id: String,
}

#[derive(Debug, ToSchema)]
#[derive(ToSchema)]
pub struct AppState {
pub jobs_state: Mutex<HashMap<String, handlers::FlistState>>,
pub db: Arc<dyn DB>,
pub config: Config,
}

#[derive(Debug, Default, Clone, Deserialize)]
Expand All @@ -24,8 +33,7 @@ pub struct Config {

pub jwt_secret: String,
pub jwt_expire_hours: i64,

pub users: Vec<auth::User>,
pub users: Vec<User>,
}

/// Parse the config file into Config struct.
Expand Down
31 changes: 31 additions & 0 deletions fl-server/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub username: String,
pub password: String,
}

pub trait DB: Send + Sync {
fn get_user_by_username(&self, username: &str) -> Option<&User>;
}

#[derive(Debug, ToSchema)]
pub struct VecDB {
users: Vec<User>,
}

impl VecDB {
pub fn new(users: &[User]) -> Self {
Self {
users: users.to_vec(),
}
}
}

impl DB for VecDB {
fn get_user_by_username(&self, username: &str) -> Option<&User> {
self.users.iter().find(|u| u.username == username)
}
}
106 changes: 49 additions & 57 deletions fl-server/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use axum::{
};
use axum_macros::debug_handler;
use std::{collections::HashMap, fs, sync::Arc};
use tokio::io;

use bollard::auth::DockerCredentials;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -78,10 +77,10 @@ pub async fn health_check_handler() -> ResponseResult {
#[debug_handler]
pub async fn create_flist_handler(
State(state): State<Arc<config::AppState>>,
Extension(cfg): Extension<config::Config>,
Extension(username): Extension<String>,
Json(body): Json<FlistBody>,
) -> impl IntoResponse {
let cfg = state.config.clone();
let credentials = Some(DockerCredentials {
username: body.username,
password: body.password,
Expand All @@ -98,37 +97,28 @@ pub async fn create_flist_handler(
}

let fl_name = docker_image.replace([':', '/'], "-") + ".fl";
let username_dir = format!("{}/{}", cfg.flist_dir, username);
let username_dir = std::path::Path::new(&cfg.flist_dir).join(&username);
let fl_path = username_dir.join(&fl_name);

match flist_exists(std::path::Path::new(&username_dir), &fl_name).await {
Ok(exists) => {
if exists {
return Err(ResponseError::Conflict("flist already exists".to_string()));
}
}
Err(e) => {
log::error!("failed to check flist existence with error {:?}", e);
return Err(ResponseError::InternalServerError);
}
if fl_path.exists() {
return Err(ResponseError::Conflict("flist already exists".to_string()));
}

let created = fs::create_dir_all(&username_dir);
if created.is_err() {
log::error!(
"failed to create user flist directory `{}` with error {:?}",
"failed to create user flist directory `{:?}` with error {:?}",
&username_dir,
created.err()
);
return Err(ResponseError::InternalServerError);
}

let fl_path: String = format!("{}/{}", username_dir, fl_name);

let meta = match Writer::new(&fl_path).await {
Ok(writer) => writer,
Err(err) => {
log::error!(
"failed to create a new writer for flist `{}` with error {}",
"failed to create a new writer for flist `{:?}` with error {}",
fl_path,
err
);
Expand All @@ -150,17 +140,30 @@ pub async fn create_flist_handler(
};
let current_job = job.clone();

state.jobs_state.lock().unwrap().insert(
job.id.clone(),
FlistState::Accepted(format!("flist '{}' is accepted", fl_name)),
);

tokio::spawn(async move {
state.jobs_state.lock().unwrap().insert(
state
.jobs_state
.lock()
.expect("failed to lock state")
.insert(
job.id.clone(),
FlistState::Started(format!("flist '{}' is started", fl_name)),
FlistState::Accepted(format!("flist '{}' is accepted", &fl_name)),
);

let flist_download_url = std::path::Path::new(&format!("{}:{}", cfg.host, cfg.port))
.join(cfg.flist_dir)
.join(username)
.join(&fl_name);

tokio::spawn(async move {
state
.jobs_state
.lock()
.expect("failed to lock state")
.insert(
job.id.clone(),
FlistState::Started(format!("flist '{}' is started", fl_name)),
);

let res = docker2fl::convert(meta, store, &docker_image, credentials).await;

// remove the file created with the writer if fl creation failed
Expand All @@ -170,18 +173,22 @@ pub async fn create_flist_handler(
state
.jobs_state
.lock()
.unwrap()
.expect("failed to lock state")
.insert(job.id.clone(), FlistState::Failed);
return;
}

state.jobs_state.lock().unwrap().insert(
job.id.clone(),
FlistState::Created(format!(
"flist {}:{}/{}/{}/{} is created successfully",
cfg.host, cfg.port, cfg.flist_dir, username, fl_name
)),
);
state
.jobs_state
.lock()
.expect("failed to lock state")
.insert(
job.id.clone(),
FlistState::Created(format!(
"flist {:?} is created successfully",
flist_download_url
)),
);
});

Ok(ResponseResult::FlistCreated(current_job))
Expand Down Expand Up @@ -209,7 +216,7 @@ pub async fn get_flist_state_handler(
if !&state
.jobs_state
.lock()
.unwrap()
.expect("failed to lock state")
.contains_key(&flist_job_id.clone())
{
return Err(ResponseError::NotFound("flist doesn't exist".to_string()));
Expand All @@ -218,9 +225,9 @@ pub async fn get_flist_state_handler(
let res_state = state
.jobs_state
.lock()
.unwrap()
.expect("failed to lock state")
.get(&flist_job_id.clone())
.unwrap()
.expect("failed to get from state")
.to_owned();

match res_state {
Expand All @@ -230,7 +237,7 @@ pub async fn get_flist_state_handler(
state
.jobs_state
.lock()
.unwrap()
.expect("failed to lock state")
.remove(&flist_job_id.clone());

Ok(ResponseResult::FlistState(res_state))
Expand All @@ -239,10 +246,10 @@ pub async fn get_flist_state_handler(
state
.jobs_state
.lock()
.unwrap()
.expect("failed to lock state")
.remove(&flist_job_id.clone());

return Err(ResponseError::InternalServerError);
Err(ResponseError::InternalServerError)
}
}
}
Expand All @@ -258,16 +265,15 @@ pub async fn get_flist_state_handler(
)
)]
#[debug_handler]
pub async fn list_flists_handler(Extension(cfg): Extension<config::Config>) -> impl IntoResponse {
pub async fn list_flists_handler(State(state): State<Arc<config::AppState>>) -> impl IntoResponse {
let mut flists: HashMap<String, Vec<FileInfo>> = HashMap::new();

let rs = visit_dir_one_level(std::path::Path::new(&cfg.flist_dir)).await;
let rs = visit_dir_one_level(&state.config.flist_dir).await;
match rs {
Ok(files) => {
for file in files {
if !file.is_file {
let flists_per_username =
visit_dir_one_level(std::path::Path::new(&file.path_uri)).await;
let flists_per_username = visit_dir_one_level(&file.path_uri).await;
match flists_per_username {
Ok(files) => flists.insert(file.name, files),
Err(e) => {
Expand All @@ -286,17 +292,3 @@ pub async fn list_flists_handler(Extension(cfg): Extension<config::Config>) -> i

Ok(ResponseResult::Flists(flists))
}

pub async fn flist_exists(dir_path: &std::path::Path, flist_name: &String) -> io::Result<bool> {
let mut dir = tokio::fs::read_dir(dir_path).await?;

while let Some(child) = dir.next_entry().await? {
let file_name = child.file_name().to_string_lossy().to_string();

if file_name.eq(flist_name) {
return Ok(true);
}
}

Ok(false)
}
Loading

0 comments on commit 2efda5e

Please sign in to comment.