From f2fb7a61abee50898517003fbadfbe6efd754819 Mon Sep 17 00:00:00 2001 From: tl-flavio-barinas Date: Thu, 24 Mar 2022 15:42:30 +0000 Subject: [PATCH 1/5] add rust tl-signature example --- rust/.gitignore | 2 +- rust/examples/sign-request/Cargo.toml | 12 +++++++ rust/examples/sign-request/README.md | 14 ++++++++ rust/examples/sign-request/src/main.rs | 50 ++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 rust/examples/sign-request/Cargo.toml create mode 100644 rust/examples/sign-request/README.md create mode 100644 rust/examples/sign-request/src/main.rs diff --git a/rust/.gitignore b/rust/.gitignore index 96ef6c0b..58f4d267 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -1,2 +1,2 @@ -/target +**/target Cargo.lock diff --git a/rust/examples/sign-request/Cargo.toml b/rust/examples/sign-request/Cargo.toml new file mode 100644 index 00000000..b978e4da --- /dev/null +++ b/rust/examples/sign-request/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sign-request" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +rand = "0.8.4" +reqwest = "0.11" +tokio = { version = "1.17", features = ["full"] } +truelayer-signing = "0.1.3" +uuid = { version = "0.8", features = ["v4"] } \ No newline at end of file diff --git a/rust/examples/sign-request/README.md b/rust/examples/sign-request/README.md new file mode 100644 index 00000000..ad0e39cc --- /dev/null +++ b/rust/examples/sign-request/README.md @@ -0,0 +1,14 @@ +# rust request signature example +Sends a signed request to `https://api.truelayer-sandbox.com/test-signature`. + +## Run + +Set environment variables: +* `ACCESS_TOKEN` A valid JWT access token for `payments` scope [docs](https://docs.truelayer.com/docs/retrieve-a-token-in-your-server). +* `KID` The certificate/key UUID for associated with your public key uploaded to console.truelayer.com. +* `PRIVATE_KEY` Private key PEM string that matches the `KID` & uploaded public key. + Should have the same format as [this example private key](https://github.com/TrueLayer/truelayer-signing/blob/main/test-resources/ec512-private.pem). + +```sh +$ cargo run +``` \ No newline at end of file diff --git a/rust/examples/sign-request/src/main.rs b/rust/examples/sign-request/src/main.rs new file mode 100644 index 00000000..ff0f1c24 --- /dev/null +++ b/rust/examples/sign-request/src/main.rs @@ -0,0 +1,50 @@ +use rand; +use std::env; +use uuid::Uuid; + +const KID: &str = "KID"; +const ACCESS_TOKEN: &str = "ACCESS_TOKEN"; +const PRIVATE_KEY: &str = "PRIVATE_KEY"; + +const URL: &str = "https://api.truelayer-sandbox.com/test-signature"; + +#[tokio::main] +async fn main() { + // load env vars + let kid = env::var(KID).expect("Missing env var KID"); + let access_token = env::var(ACCESS_TOKEN).expect("Missing env var ACCESS_TOKEN"); + let private_key = env::var(PRIVATE_KEY).expect("Missing env var PRIVATE_KEY"); + + // create idemoptency key and body + let idempotency_key = Uuid::new_v4().to_string(); + let body = format!("body-{}", rand::random::()); + + // generate tl-signature + let tl_signature = truelayer_signing::sign_with_pem(kid.as_str(), private_key.as_bytes()) + .method("POST") + .path("/test-signature") + .header("Idempotency-Key", idempotency_key.as_bytes()) + .header("X-Bar-Header", b"abc123") + .body(body.as_bytes()) + .sign() + .unwrap(); + + // call `/test-signature` endpoint + let client = reqwest::Client::new(); + let response = client + .post(URL) + .header("Authorization", format!("Bearer {access_token}")) + .header("Idempotency-Key", idempotency_key) + .header("X-Bar-Header", "abc123") + .header("Tl-Signature", tl_signature) + .body(body) + .send() + .await + .unwrap(); + + // print result + match response.error_for_status_ref() { + Ok(res) => println!("{} ✓", res.status()), + Err(err) => println!("{}", err.status().unwrap()), + } +} From 119a31da16aa4b6bb09b073d9aa8d0c26483d132 Mon Sep 17 00:00:00 2001 From: Flavio B <95243153+tl-flavio-barinas@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:04:38 +0000 Subject: [PATCH 2/5] use inline strings for env-var names Co-authored-by: Alex Butler --- rust/examples/sign-request/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/examples/sign-request/src/main.rs b/rust/examples/sign-request/src/main.rs index ff0f1c24..d4b6c5ab 100644 --- a/rust/examples/sign-request/src/main.rs +++ b/rust/examples/sign-request/src/main.rs @@ -11,9 +11,9 @@ const URL: &str = "https://api.truelayer-sandbox.com/test-signature"; #[tokio::main] async fn main() { // load env vars - let kid = env::var(KID).expect("Missing env var KID"); - let access_token = env::var(ACCESS_TOKEN).expect("Missing env var ACCESS_TOKEN"); - let private_key = env::var(PRIVATE_KEY).expect("Missing env var PRIVATE_KEY"); + let kid = env::var("KID").expect("Missing env var KID"); + let access_token = env::var("ACCESS_TOKEN").expect("Missing env var ACCESS_TOKEN"); + let private_key = env::var("PRIVATE_KEY").expect("Missing env var PRIVATE_KEY"); // create idemoptency key and body let idempotency_key = Uuid::new_v4().to_string(); From eafddd496b69dbb8c00a64c677ccae7e80d95761 Mon Sep 17 00:00:00 2001 From: tl-flavio-barinas Date: Thu, 24 Mar 2022 17:11:53 +0000 Subject: [PATCH 3/5] remove path from base url + add body to error print --- rust/examples/sign-request/src/main.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rust/examples/sign-request/src/main.rs b/rust/examples/sign-request/src/main.rs index d4b6c5ab..48b616a3 100644 --- a/rust/examples/sign-request/src/main.rs +++ b/rust/examples/sign-request/src/main.rs @@ -2,11 +2,8 @@ use rand; use std::env; use uuid::Uuid; -const KID: &str = "KID"; -const ACCESS_TOKEN: &str = "ACCESS_TOKEN"; -const PRIVATE_KEY: &str = "PRIVATE_KEY"; - -const URL: &str = "https://api.truelayer-sandbox.com/test-signature"; +// the base url to use +const TL_BASE_URL: &str = "https://api.truelayer-sandbox.com"; #[tokio::main] async fn main() { @@ -32,7 +29,7 @@ async fn main() { // call `/test-signature` endpoint let client = reqwest::Client::new(); let response = client - .post(URL) + .post(format!("{}/test-signature", TL_BASE_URL)) .header("Authorization", format!("Bearer {access_token}")) .header("Idempotency-Key", idempotency_key) .header("X-Bar-Header", "abc123") @@ -45,6 +42,10 @@ async fn main() { // print result match response.error_for_status_ref() { Ok(res) => println!("{} ✓", res.status()), - Err(err) => println!("{}", err.status().unwrap()), + Err(err) => println!( + "{}: {}", + err.status().unwrap(), + response.text().await.unwrap() + ), } } From aeb60c70f9289ed684caa40ba85052b155833a48 Mon Sep 17 00:00:00 2001 From: tl-flavio-barinas Date: Thu, 24 Mar 2022 17:14:40 +0000 Subject: [PATCH 4/5] add link to readme --- rust/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/README.md b/rust/README.md index 3c3cea90..d73cd8e1 100644 --- a/rust/README.md +++ b/rust/README.md @@ -14,6 +14,8 @@ let tl_signature = truelayer_signing::sign_with_pem(kid, private_key) .sign()?; ``` +See [full example](./examples/sign-request/). + ## Prerequisites - OpenSSL (see [here](https://www.openssl.org/) for instructions). From ef423437d4476f231ae8156e31d632cd362d1a14 Mon Sep 17 00:00:00 2001 From: tl-flavio-barinas Date: Fri, 25 Mar 2022 09:18:12 +0000 Subject: [PATCH 5/5] update comments --- rust/examples/sign-request/src/main.rs | 38 ++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/rust/examples/sign-request/src/main.rs b/rust/examples/sign-request/src/main.rs index 48b616a3..de9e0680 100644 --- a/rust/examples/sign-request/src/main.rs +++ b/rust/examples/sign-request/src/main.rs @@ -7,27 +7,31 @@ const TL_BASE_URL: &str = "https://api.truelayer-sandbox.com"; #[tokio::main] async fn main() { - // load env vars + // Read required env vars let kid = env::var("KID").expect("Missing env var KID"); let access_token = env::var("ACCESS_TOKEN").expect("Missing env var ACCESS_TOKEN"); let private_key = env::var("PRIVATE_KEY").expect("Missing env var PRIVATE_KEY"); - // create idemoptency key and body - let idempotency_key = Uuid::new_v4().to_string(); + // A random body string is enough for this request as `/test-signature` endpoint does not + // require any schema, it simply checks the signature is valid against what's received. let body = format!("body-{}", rand::random::()); - // generate tl-signature + let idempotency_key = Uuid::new_v4().to_string(); + + // Generate tl-signature let tl_signature = truelayer_signing::sign_with_pem(kid.as_str(), private_key.as_bytes()) - .method("POST") - .path("/test-signature") + .method("POST") // as we're sending a POST request + .path("/test-signature") // the path of our request + // Optional: /test-signature does not require any headers, but we may sign some anyway. + // All signed headers *must* be included unmodified in the request. .header("Idempotency-Key", idempotency_key.as_bytes()) .header("X-Bar-Header", b"abc123") - .body(body.as_bytes()) + .body(body.as_bytes()) // body of our request .sign() .unwrap(); - // call `/test-signature` endpoint let client = reqwest::Client::new(); + // Request body & any signed headers *must* exactly match what was used to generate the signature. let response = client .post(format!("{}/test-signature", TL_BASE_URL)) .header("Authorization", format!("Bearer {access_token}")) @@ -39,13 +43,13 @@ async fn main() { .await .unwrap(); - // print result - match response.error_for_status_ref() { - Ok(res) => println!("{} ✓", res.status()), - Err(err) => println!( - "{}: {}", - err.status().unwrap(), - response.text().await.unwrap() - ), - } + let status = response.status(); + let response_body = match status.is_success() { + true => String::from("✓"), + false => response.text().await.unwrap(), + }; + + // 204 means success + // 401 means either the access token is invalid, or the signature is invalid. + println!("{} {}", status.as_u16(), response_body); }