Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c9780b9
issue-auth debug for client secret
smart--petea Nov 24, 2023
a4e1067
issue-auth refactor
smart--petea Nov 24, 2023
a49e5c7
issue-auth skip_serializing_if
smart--petea Nov 25, 2023
8c510f7
issue-auth to_string
smart--petea Nov 25, 2023
02fe07d
issue-auth removed set_id in client add
smart--petea Nov 25, 2023
a2c7bf6
issue-auth client middleware + JsonResponse
smart--petea Nov 25, 2023
53e9669
issue-auth set_msg
smart--petea Nov 25, 2023
53c8505
issue-auth removed todo
smart--petea Nov 25, 2023
c8a5ca2
issue-auth PayloadError
smart--petea Nov 25, 2023
c562ddc
issue-auth console.rs
smart--petea Nov 26, 2023
e3c8dc8
issue-auth default-run
smart--petea Nov 26, 2023
9643121
command
smart--petea Nov 26, 2023
003e16c
issue-auth removed client.id = 1
smart--petea Nov 26, 2023
5009fa3
issue-auth add_handler_internal
smart--petea Nov 26, 2023
e8fed13
issue-auth
smart--petea Nov 26, 2023
fa7f675
issue-auth console
smart--petea Nov 26, 2023
613e905
issue-auth remove redundant Arc
smart--petea Nov 27, 2023
2ccb733
issue-auth console
smart--petea Nov 27, 2023
279e29a
issue-auth call thing
smart--petea Nov 28, 2023
cd26401
issue-auth rename console.rs
smart--petea Nov 28, 2023
90d41aa
issue-auth console command
smart--petea Nov 28, 2023
f5f6a07
issue-auth actix runtime
smart--petea Nov 28, 2023
cda917b
issue-auth
smart--petea Nov 28, 2023
f2ac4b1
issue-auth
smart--petea Nov 29, 2023
507dc56
issue-auth middleware/client optimisation
smart--petea Dec 1, 2023
caee9a4
issue-auth tests/common.rs
smart--petea Dec 2, 2023
f62296c
issue-auth no common test target
smart--petea Dec 2, 2023
38da8d4
issue-auth no common test target
smart--petea Dec 2, 2023
14a8a36
issue-auth test middleware_client sketch
smart--petea Dec 2, 2023
d71533b
issue-auth test/deploy JsonResponse
smart--petea Dec 4, 2023
a0d7239
issue-auth optimising middleware client
smart--petea Dec 4, 2023
c11fec4
issue-auth working on optimisations
smart--petea Dec 5, 2023
29f0095
issue-auth userform
smart--petea Dec 5, 2023
61e6dd0
issue-auth trydirect middleware
smart--petea Dec 6, 2023
7ea0f30
issue-auth optimized use block in trydirect.rs
smart--petea Dec 7, 2023
e3b9efd
issue-auth wiremock --dev
smart--petea Dec 7, 2023
7b4df71
issue-auth tests/middleware_trydirect.rs
smart--petea Dec 7, 2023
d86e37f
issue-auth - db_fetch_client
smart--petea Dec 8, 2023
3f9f6d0
issue-auth - optimize src/middleware/client.rs
smart--petea Dec 8, 2023
093a0c9
issue-auth Arc<User>
smart--petea Dec 9, 2023
107a66b
issue-auth db_count_clients_by_user
smart--petea Dec 9, 2023
581786d
issue-auth src/routes/client/add.rs
smart--petea Dec 9, 2023
7669952
issue-auth disable client route
smart--petea Dec 10, 2023
e8f63e6
issue-auth optimize /client/{id}/enable
smart--petea Dec 10, 2023
013014a
issue-auth merge with dev
smart--petea Dec 11, 2023
18d20a1
issue-auth configuration.yaml.dist
smart--petea Dec 11, 2023
01ebbe9
Merge branch 'dev' into issue-auth
smart--petea Dec 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 294 additions & 15 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
name = "stacker"
version = "0.1.0"
edition = "2021"
default-run= "server"

[lib]
path="src/lib.rs"

[[bin]]
path = "src/main.rs"
name = "stacker"
name = "server"

[[bin]]
path = "src/console/main.rs"
name = "console"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down Expand Up @@ -46,6 +51,7 @@ indexmap = { version = "2.0.0", features = ["serde"], optional = true }
serde_yaml = "0.9"
lapin = { version = "2.3.1", features = ["serde_json"] }
futures-lite = "1.13.0"
clap = { version = "4.4.8", features = ["derive"] }

[dependencies.sqlx]
version = "0.6.3"
Expand All @@ -65,3 +71,4 @@ indexmap = ["dep:indexmap"]

[dev-dependencies]
glob = "0.3"
wiremock = "0.5.22"
17 changes: 17 additions & 0 deletions configuration.yaml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#auth_url: http://127.0.0.1:8080/me
app_host: 127.0.0.1
app_port: 8000
auth_url: https://dev.try.direct/server/user/oauth_server/api/me
max_clients_number: 2
database:
host: 127.0.0.1
port: 5432
username: postgres
password: postgres
database_name: stacker

amqp:
host: 127.0.0.1
port: 5672
username: guest
password: guest
3 changes: 3 additions & 0 deletions src/console/commands/appclient/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod new;

pub use new::*;
40 changes: 40 additions & 0 deletions src/console/commands/appclient/new.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::configuration::get_configuration;
use actix_web::rt;
use actix_web::web;
use sqlx::PgPool;

pub struct NewCommand {
user_id: i32,
}

impl NewCommand {
pub fn new(user_id: i32) -> Self {
Self { user_id }
}
}

impl crate::console::commands::CallableTrait for NewCommand {
fn call(&self) -> Result<(), Box<dyn std::error::Error>> {
rt::System::new().block_on(async {
let settings = get_configuration().expect("Failed to read configuration.");
let db_pool = PgPool::connect(&settings.database.connection_string())
.await
.expect("Failed to connect to database.");

let settings = web::Data::new(settings);
let db_pool = web::Data::new(db_pool);

//todo get user from trydirect
let user = crate::models::user::User {
id: "first_name".to_string(),
first_name: "first_name".to_string(),
last_name: "last_name".to_string(),
email: "email".to_string(),
email_confirmed: true,
};
crate::routes::client::add_handler_inner(&user.id, settings, db_pool).await?;

Ok(())
})
}
}
3 changes: 3 additions & 0 deletions src/console/commands/callable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub trait CallableTrait {
fn call(&self) -> Result<(), Box<dyn std::error::Error>>;
}
4 changes: 4 additions & 0 deletions src/console/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod appclient;
mod callable;

pub use callable::*;
43 changes: 43 additions & 0 deletions src/console/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use clap::{Parser, Subcommand};

#[derive(Parser, Debug)]
struct Cli {
#[command(subcommand)]
command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
AppClient {
#[command(subcommand)]
command: AppClientCommands,
},
}

#[derive(Debug, Subcommand)]
enum AppClientCommands {
New {
#[arg(long)]
user_id: i32,
},
}

//todo add documentation about how to add a new command
//todo the helper from console should have a nicer display

fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();

get_command(cli)?.call()
}

fn get_command(cli: Cli) -> Result<Box<dyn stacker::console::commands::CallableTrait>, String> {
match cli.command {
Commands::AppClient { command } => match command {
AppClientCommands::New { user_id } => Ok(Box::new(
stacker::console::commands::appclient::NewCommand::new(user_id),
)),
},
_ => Err("command does not match".to_string()),
}
}
1 change: 1 addition & 0 deletions src/console/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod commands;
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod configuration;
pub mod console;
pub mod forms;
pub mod helpers;
mod middleware;
Expand Down
144 changes: 74 additions & 70 deletions src/middleware/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::helpers::JsonResponse;
use crate::models::Client;
use actix_http::header::CONTENT_LENGTH;
use actix_web::error::{ErrorForbidden, ErrorInternalServerError, ErrorNotFound, PayloadError};
Expand Down Expand Up @@ -73,86 +74,42 @@ where
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let service = self.service.clone();
async move {
let client_id: i32 = get_header(&req, "stacker-id").map_err(|m| ErrorBadRequest(m))?;
let hash: String = get_header(&req, "stacker-hash").map_err(|m| ErrorBadRequest(m))?;

let query_span = tracing::info_span!("Fetching the client by ID");
let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap();

let mut client: Client = match sqlx::query_as!(
Client,
r#"
SELECT
id, user_id, secret
FROM client c
WHERE c.id = $1
"#,
client_id,
)
.fetch_one(db_pool.get_ref())
.instrument(query_span)
.await
{
Ok(client) if client.secret.is_some() => client,
Ok(_client) => {
return Err(ErrorForbidden("client is not active"));
}
Err(sqlx::Error::RowNotFound) => {
return Err(ErrorNotFound("the client is not found"));
}
Err(e) => {
tracing::error!("Failed to execute fetch query: {:?}", e);
let client_id: i32 = get_header(&req, "stacker-id")?;
let header_hash: String = get_header(&req, "stacker-hash")?;

return Err(ErrorInternalServerError(""));
}
};

let content_length: usize =
get_header(&req, CONTENT_LENGTH.as_str()).map_err(|m| ErrorBadRequest(m))?;
let body = req
.take_payload()
.fold(
BytesMut::with_capacity(content_length),
|mut body, chunk| {
let chunk = chunk.unwrap(); //todo process the potential error of unwrap
body.extend_from_slice(&chunk); //todo

ready(body)
},
)
.await;

let mut mac =
match Hmac::<Sha256>::new_from_slice(client.secret.as_ref().unwrap().as_bytes()) {
Ok(mac) => mac,
Err(err) => {
tracing::error!("error generating hmac {err:?}");

return Err(ErrorInternalServerError(""));
}
};

mac.update(body.as_ref());
let computed_hash = format!("{:x}", mac.finalize().into_bytes());
if hash != computed_hash {
return Err(ErrorBadRequest("hash is wrong"));
let db_pool = req.app_data::<web::Data<Pool<Postgres>>>().unwrap().get_ref();
let mut client: Client = db_fetch_client(db_pool, client_id).await?;
if client.secret.is_none() {
return Err("client is not active".to_string());
}

let (_, mut payload) = actix_http::h1::Payload::create(true);
payload.unread_data(body.into());
req.set_payload(payload.into());
let client_secret = client.secret.as_ref().unwrap().as_bytes();
let body_hash = compute_body_hash(&mut req, client_secret).await?;
if header_hash != body_hash {
return Err("hash is wrong".to_string());
}

match req.extensions_mut().insert(Arc::new(client)) {
Some(_) => {
tracing::error!("client middleware already called once");
return Err(ErrorInternalServerError(""));
return Err("".to_string());
}
None => {}
}

let service = service.lock().await;
service.call(req).await
Ok(req)
}
.then(|req| async move {
match req {
Ok(req) => {
let service = service.lock().await;
service.call(req).await
}
Err(msg) => Err(ErrorBadRequest(
JsonResponse::<Client>::build().set_msg(msg).to_string(),
)),
}
})
.boxed_local()
}
}
Expand All @@ -168,9 +125,56 @@ where

let header_value: &str = header_value
.to_str()
.map_err(|_| format!("header {header_name} can't be converted to string"))?; //map_err
//
.map_err(|_| format!("header {header_name} can't be converted to string"))?;

header_value
.parse::<T>()
.map_err(|_| format!("header {header_name} has wrong type"))
}

async fn db_fetch_client(db_pool: &Pool<Postgres>, client_id: i32) -> Result<Client, String> {
let query_span = tracing::info_span!("Fetching the client by ID");

sqlx::query_as!(
Client,
r#"SELECT id, user_id, secret FROM client c WHERE c.id = $1"#,
client_id,
)
.fetch_one(db_pool)
.instrument(query_span)
.await
.map_err(|err| {
match err {
sqlx::Error::RowNotFound => "the client is not found".to_string(),
e => {
tracing::error!("Failed to execute fetch query: {:?}", e);
String::new()
}
}
})
}

async fn compute_body_hash(req: &mut ServiceRequest, client_secret: &[u8]) -> Result<String, String> {
let content_length: usize = get_header(req, CONTENT_LENGTH.as_str())?;
let mut body = BytesMut::with_capacity(content_length);
let mut payload = req.take_payload();
while let Some(chunk) = payload.next().await {
body.extend_from_slice(&chunk.expect("can't unwrap the chunk"));
}

let mut mac =
match Hmac::<Sha256>::new_from_slice(client_secret) {
Ok(mac) => mac,
Err(err) => {
tracing::error!("error generating hmac {err:?}");
return Err("".to_string());
}
};

mac.update(body.as_ref());
let (_, mut payload) = actix_http::h1::Payload::create(true);
payload.unread_data(body.into());
req.set_payload(payload.into());

Ok(format!("{:x}", mac.finalize().into_bytes()))
}
Loading