Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the need to send a JWT when calling request_payment, cancel_payment and mark_invoice_as_received #42

Merged
merged 1 commit into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion api/src/presentation/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ pub fn serve(
.mount(
"/api",
routes![
routes::users::profile_picture,
routes::users::update_user_profile,
routes::users::update_user_payout_info,
routes::users::search_users,
Expand Down
22 changes: 3 additions & 19 deletions api/src/presentation/http/routes/payment/cancel.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use domain::{AggregateRepository, Payment};
use http_api_problem::{HttpApiProblem, StatusCode};
use presentation::http::guards::{ApiKey, Claims, Role};
use rocket::{serde::json::Json, State};
use presentation::http::guards::ApiKey;
use rocket::serde::json::Json;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{application, domain::permissions::IntoPermission};
use crate::application;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand All @@ -17,25 +16,10 @@ pub struct Response {
pub async fn cancel_payment(
_api_key: ApiKey,
payment_id: Uuid,
claims: Option<Claims>,
role: Role,
usecase: application::payment::cancel::Usecase,
payment_repository: &State<AggregateRepository<Payment>>,
) -> Result<Json<Response>, HttpApiProblem> {
let payment_id = payment_id.into();

if !role
.to_permissions((*payment_repository).clone())
.can_cancel_payment(&payment_id)
{
return Err(HttpApiProblem::new(StatusCode::UNAUTHORIZED)
.title("Unauthorized operation on project")
.detail(format!(
"User {} needs project lead role to cancel a payment request",
claims.map(|c| c.user_id).unwrap_or_default(),
)));
}

let command_id = usecase.cancel(&payment_id).await.map_err(|e| {
HttpApiProblem::new(StatusCode::INTERNAL_SERVER_ERROR)
.title("Unable to process cancel_payment request")
Expand Down
43 changes: 7 additions & 36 deletions api/src/presentation/http/routes/payment/request.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use domain::{
AggregateRepository, CommandId, Currency, GithubUserId, Payment, PaymentId, ProjectId,
};
use domain::{CommandId, Currency, GithubUserId, PaymentId, ProjectId, UserId};
use http_api_problem::{HttpApiProblem, StatusCode};
use presentation::http::guards::{ApiKey, Claims, Role};
use rocket::{serde::json::Json, State};
use presentation::http::guards::ApiKey;
use rocket::serde::json::Json;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};

use crate::{application, domain::permissions::IntoPermission, presentation::http::dto};
use crate::{application, presentation::http::dto};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand All @@ -21,6 +19,7 @@
pub struct Request {
project_id: ProjectId,
recipient_id: GithubUserId,
requestor_id: UserId,
amount: Decimal,
currency: &'static Currency,
hours_worked: Option<u32>,
Expand All @@ -31,38 +30,22 @@
pub async fn request_payment(
_api_key: ApiKey,
request: Json<Request>,
claims: Claims,
role: Role,
payment_repository: &State<AggregateRepository<Payment>>,
request_payment_usecase: application::payment::request::Usecase,
) -> Result<Json<Response>, HttpApiProblem> {
let Request {
project_id,
recipient_id,
requestor_id,

Check warning on line 38 in api/src/presentation/http/routes/payment/request.rs

View check run for this annotation

Codecov / codecov/patch

api/src/presentation/http/routes/payment/request.rs#L38

Added line #L38 was not covered by tests
amount,
currency,
hours_worked,
reason,
} = request.into_inner();

let caller_id = claims.user_id;

if !role
.to_permissions((*payment_repository).clone())
.can_spend_budget_of_project(&project_id)
{
return Err(HttpApiProblem::new(StatusCode::UNAUTHORIZED)
.title("Unauthorized operation on project")
.detail(format!(
"User {} needs project lead role to create a payment request on project {}",
caller_id, project_id
)));
}

let (payment_id, command_id) = request_payment_usecase
.request(
project_id,
caller_id,
requestor_id,

Check warning on line 48 in api/src/presentation/http/routes/payment/request.rs

View check run for this annotation

Codecov / codecov/patch

api/src/presentation/http/routes/payment/request.rs#L48

Added line #L48 was not covered by tests
recipient_id,
amount,
currency,
Expand Down Expand Up @@ -98,22 +81,10 @@
pub async fn mark_invoice_as_received(
_api_key: ApiKey,
request: Json<InvoiceReceivedRequest>,
role: Role,
payment_repository: &State<AggregateRepository<Payment>>,
invoice_payment_usecase: application::payment::invoice::Usecase,
) -> Result<(), HttpApiProblem> {
let InvoiceReceivedRequest { payments } = request.into_inner();

let caller_permissions = role.to_permissions((*payment_repository).clone());

if payments
.iter()
.any(|payment_id| !caller_permissions.can_mark_invoice_as_received_for_payment(payment_id))
{
return Err(HttpApiProblem::new(StatusCode::UNAUTHORIZED)
.title("Only recipient can mark invoice as received"));
}

invoice_payment_usecase.mark_invoice_as_received(payments).await.map_err(|e| {
{
HttpApiProblem::new(StatusCode::INTERNAL_SERVER_ERROR)
Expand Down
6 changes: 4 additions & 2 deletions api/tests/context/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use rocket::{http::Header, serde::json::json};

use super::API_KEY;

pub const USER_ID: &str = "9b7effeb-963f-4ac4-be74-d735501925ed";

#[allow(unused)]
pub fn jwt(project_leaded_id: Option<String>) -> String {
let now = SystemTime::now()
Expand All @@ -27,10 +29,10 @@ pub fn jwt(project_leaded_id: Option<String>) -> String {
"registered_user"
],
"x-hasura-default-role": "registered_user",
"x-hasura-user-id": "9b7effeb-963f-4ac4-be74-d735501925ed",
"x-hasura-user-id": USER_ID,
"x-hasura-user-is-anonymous": "false"
},
"sub": "9b7effeb-963f-4ac4-be74-d735501925ed",
"sub": USER_ID,
"iat": now,
"exp": now + 1000,
"iss": "hasura-auth-unit-tests"
Expand Down
66 changes: 14 additions & 52 deletions api/tests/payment_it.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use uuid::Uuid;

use crate::context::{
docker,
utils::{api_key_header, jwt},
utils::{api_key_header, USER_ID},
Context,
};

Expand All @@ -52,16 +52,16 @@ pub async fn payment_processing(docker: &'static Cli) {
test.project_lead_can_request_payments_in_usdc()
.await
.expect("project_lead_can_request_payments_in_usdc");
test.anyone_cannot_request_payments()
test.cannot_request_payments_without_api_key()
.await
.expect("anyone_cannot_request_payments");
.expect("cannot_request_payments_without_api_key");
test.project_lead_can_cancel_payments()
.await
.expect("project_lead_can_cancel_payments");
test.admin_can_cancel_payments().await.expect("admin_can_cancel_payments");
test.anyone_cannot_cancel_payments()
test.cannot_cancel_payments_without_api_key()
.await
.expect("anyone_cannot_cancel_payments");
.expect("cannot_cancel_payments_without_api_key");

test.admin_can_add_a_sepa_receipt().await.expect("admin_can_add_a_sepa_receipt");
test.admin_can_add_an_eth_receipt().await.expect("admin_can_add_an_eth_receipt");
Expand Down Expand Up @@ -118,6 +118,7 @@ impl<'a> Test<'a> {
let request = json!({
"projectId": project_id,
"recipientId": 595505,
"requestorId": USER_ID,
"amount": 10,
"currency": "USD",
"hoursWorked": 1,
Expand All @@ -138,11 +139,6 @@ impl<'a> Test<'a> {
.post("/api/payments")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(Some(project_id.to_string()))),
))
.body(request.to_string())
.dispatch()
.await;
Expand Down Expand Up @@ -242,6 +238,7 @@ impl<'a> Test<'a> {
let request = json!({
"projectId": project_id,
"recipientId": 595505,
"requestorId": USER_ID,
"amount": 0.00001,
"currency": "ETH",
"reason": {
Expand All @@ -261,11 +258,6 @@ impl<'a> Test<'a> {
.post("/api/payments")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(Some(project_id.to_string()))),
))
.body(request.to_string())
.dispatch()
.await;
Expand Down Expand Up @@ -365,6 +357,7 @@ impl<'a> Test<'a> {
let request = json!({
"projectId": project_id,
"recipientId": 595505,
"requestorId": USER_ID,
"amount": 100.52,
"currency": "LORDS",
"reason": {
Expand All @@ -384,11 +377,6 @@ impl<'a> Test<'a> {
.post("/api/payments")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(Some(project_id.to_string()))),
))
.body(request.to_string())
.dispatch()
.await;
Expand Down Expand Up @@ -485,6 +473,7 @@ impl<'a> Test<'a> {
let request = json!({
"projectId": project_id,
"recipientId": 595505,
"requestorId": USER_ID,
"amount": 100.52,
"currency": "USDC",
"reason": {
Expand All @@ -504,11 +493,6 @@ impl<'a> Test<'a> {
.post("/api/payments")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(Some(project_id.to_string()))),
))
.body(request.to_string())
.dispatch()
.await;
Expand Down Expand Up @@ -570,8 +554,8 @@ impl<'a> Test<'a> {
Ok(())
}

async fn anyone_cannot_request_payments(&mut self) -> Result<()> {
info!("anyone_cannot_request_payments");
async fn cannot_request_payments_without_api_key(&mut self) -> Result<()> {
info!("cannot_request_payments_without_api_key");

// Given
let project_id = ProjectId::new();
Expand Down Expand Up @@ -604,6 +588,7 @@ impl<'a> Test<'a> {
let request = json!({
"projectId": project_id,
"recipientId": 595505,
"requestorId": USER_ID,
"amount": 10,
"currency": "USD",
"hoursWorked": 1,
Expand All @@ -623,12 +608,6 @@ impl<'a> Test<'a> {
.http_client
.post("/api/payments")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(None)),
))
.body(request.to_string())
.dispatch()
.await;
Expand Down Expand Up @@ -694,11 +673,6 @@ impl<'a> Test<'a> {
.delete(format!("/api/payments/{payment_id}"))
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(Some(project_id.to_string()))),
))
.dispatch()
.await;

Expand Down Expand Up @@ -776,7 +750,6 @@ impl<'a> Test<'a> {
.delete(format!("/api/payments/{payment_id}"))
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "admin"))
.dispatch()
.await;

Expand Down Expand Up @@ -804,8 +777,8 @@ impl<'a> Test<'a> {
Ok(())
}

async fn anyone_cannot_cancel_payments(&mut self) -> Result<()> {
info!("anyone_cannot_cancel_payments");
async fn cannot_cancel_payments_without_api_key(&mut self) -> Result<()> {
info!("cannot_cancel_payments_without_api_key");

// Given
let project_id = ProjectId::new();
Expand Down Expand Up @@ -853,12 +826,6 @@ impl<'a> Test<'a> {
.http_client
.delete(format!("/api/payments/{payment_id}"))
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(None)),
))
.dispatch()
.await;

Expand Down Expand Up @@ -1469,11 +1436,6 @@ impl<'a> Test<'a> {
.put("/api/payments/invoiceReceivedAt")
.header(ContentType::JSON)
.header(api_key_header())
.header(Header::new("x-hasura-role", "registered_user"))
.header(Header::new(
"Authorization",
format!("Bearer {}", jwt(None)),
))
.body(request.to_string())
.dispatch()
.await;
Expand Down
Loading