From b39d7efa8c71bc687aba49a960d9e9043036e3d0 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Thu, 1 Aug 2024 18:54:17 +0530 Subject: [PATCH 01/10] added rust flake --- .envrc | 1 + flake.lock | 27 +++++++++++++++++++++++++++ flake.nix | 19 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7adbffd --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1722185531, + "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..986d09f --- /dev/null +++ b/flake.nix @@ -0,0 +1,19 @@ +{ + description = "Rust flake"; + inputs = + { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; # or whatever vers + }; + + outputs = { self, nixpkgs, ... }@inputs: + let + system = "aarch64-darwin"; # your version + pkgs = nixpkgs.legacyPackages.${system}; + in + { + devShells.${system}.default = pkgs.mkShell + { + packages = with pkgs; [ rustup cargo ]; # whatever you need + }; + }; +} \ No newline at end of file From c906250ce7def2c27bc40e9e14ca0e8bb08cd7a5 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Tue, 6 Aug 2024 22:26:39 +0530 Subject: [PATCH 02/10] added namespaced identities --- Cargo.lock | 1 + ssr/Cargo.toml | 1 + ssr/src/api/mod.rs | 4 +- ssr/src/api/server_impl/mod.rs | 89 ++++++++++++++++++++++++---------- types/src/lib.rs | 1 + 5 files changed, 68 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d1c03b..9202192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5614,6 +5614,7 @@ dependencies = [ "leptos_router", "log", "openidconnect", + "rand", "rand_core", "serde", "serde_json", diff --git a/ssr/Cargo.toml b/ssr/Cargo.toml index 7ce1455..732a00a 100644 --- a/ssr/Cargo.toml +++ b/ssr/Cargo.toml @@ -39,6 +39,7 @@ icondata = "0.3.0" icondata_core = "0.1.0" url = "2.5.0" hmac = { version = "0.12.1", optional = true } +rand = { version = "0.8.5", features = ["std_rng"] } [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate", "ic-agent/wasm-bindgen"] diff --git a/ssr/src/api/mod.rs b/ssr/src/api/mod.rs index cefd86c..0130b66 100644 --- a/ssr/src/api/mod.rs +++ b/ssr/src/api/mod.rs @@ -9,12 +9,12 @@ pub mod server_impl; #[server(endpoint = "extract_or_generate", input = Json, output = Json)] pub async fn extract_or_generate_identity() -> Result { - server_impl::extract_or_generate_identity_impl().await + server_impl::extract_or_generate_identity_impl("yral".into()).await } #[server(endpoint = "logout", input = Json, output = Json)] pub async fn logout_identity() -> Result { - server_impl::logout_identity_impl().await + server_impl::logout_identity_impl("yral".into()).await } #[server(endpoint = "upgrade_refresh_claim", input = Json, output = Json)] diff --git a/ssr/src/api/server_impl/mod.rs b/ssr/src/api/server_impl/mod.rs index afed13b..c7a9a69 100644 --- a/ssr/src/api/server_impl/mod.rs +++ b/ssr/src/api/server_impl/mod.rs @@ -12,7 +12,8 @@ use ic_agent::{ export::Principal, identity::{Delegation, Identity, Secp256k1Identity, SignedDelegation}, }; -use k256::sha2::Sha256; +use k256::{elliptic_curve::SecretKey, sha2::{Digest, Sha256}, Secp256k1}; +use rand::{rngs::StdRng, SeedableRng}; use leptos::{expect_context, ServerFnError}; use leptos_axum::{extract, extract_with_state, ResponseOptions}; use rand_core::OsRng; @@ -86,11 +87,12 @@ pub fn set_cookies(resp: &ResponseOptions, jar: impl IntoResponse) { } #[cfg(feature = "oauth")] -fn refresh_claim(principal: Principal, referrer_host: url::Host) -> types::RefreshTokenClaim { +fn refresh_claim(principal: Principal, referrer_host: url::Host, namespace: String) -> types::RefreshTokenClaim { use types::REFRESH_TOKEN_CLAIM_MAX_AGE; types::RefreshTokenClaim { principal, + namespace, expiry_epoch: current_epoch() + REFRESH_TOKEN_CLAIM_MAX_AGE, referrer_host, } @@ -134,9 +136,10 @@ fn verify_refresh_claim( Ok(claim.principal) } -#[derive(Clone, Copy, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize)] struct RefreshToken { principal: Principal, + namespace: String, expiry_epoch_ms: u128, } @@ -153,7 +156,7 @@ async fn extract_principal_from_cookie( Ok(Some(token.principal)) } -async fn fetch_identity_from_kv( +async fn fetch_identity_key_from_kv( kv: &KVStoreImpl, principal: Principal, ) -> Result, ServerFnError> { @@ -171,27 +174,55 @@ pub async fn try_extract_identity( let Some(principal) = extract_principal_from_cookie(jar).await? else { return Ok(None); }; - fetch_identity_from_kv(kv, principal).await + fetch_identity_key_from_kv(kv, principal).await } -async fn generate_and_save_identity(kv: &KVStoreImpl) -> Result { +async fn generate_and_save_identity_key(kv: &KVStoreImpl) -> Result, ServerFnError> { let base_identity_key = k256::SecretKey::random(&mut OsRng); - let base_identity = Secp256k1Identity::from_private_key(base_identity_key.clone()); - let principal = base_identity.sender().unwrap(); + save_identity_in_kv(kv, base_identity_key.clone()).await?; + Ok(base_identity_key) +} + +async fn save_identity_in_kv(kv: &KVStoreImpl, identity_key: SecretKey) -> Result<(), ServerFnError> { - let base_jwk = base_identity_key.to_jwk_string(); - kv.write(principal.to_text(), base_jwk.to_string()).await?; - Ok(base_identity) + let identity = Secp256k1Identity::from_private_key(identity_key.clone()); + let principal = identity.sender().unwrap(); + let jwk = identity_key.to_jwk_string(); + kv.write(principal.to_text(), jwk.to_string()).await?; + Ok(()) } -pub async fn update_user_identity( +fn generate_namespaced_identity_key(namespace: &str, from_secret_key: SecretKey) -> SecretKey{ + let app_name = namespace.as_bytes(); + + let mut combined_bytes:Vec = Vec::new(); + combined_bytes.extend_from_slice(&from_secret_key.to_bytes()); + combined_bytes.extend_from_slice(app_name); + + let mut hasher = Sha256::new(); + hasher.update(combined_bytes); + let hashed_val = hasher.finalize(); + + let mut seed = [0u8; 32]; + seed.copy_from_slice(&hashed_val[..32]); + + k256::SecretKey::random(&mut StdRng::from_seed(seed)) +} + + + +pub async fn set_cookie_and_get_namespaced_identity( response_opts: &ResponseOptions, mut jar: SignedCookieJar, - identity: impl Identity, + identity_key: SecretKey, + namespace: &str ) -> Result { let refresh_max_age = REFRESH_MAX_AGE; + let identity = Secp256k1Identity::from_private_key(identity_key.clone()); + let principal = identity.sender().unwrap(); let refresh_token = RefreshToken { - principal: identity.sender().unwrap(), + principal, + namespace: namespace.to_owned(), expiry_epoch_ms: (current_epoch() + refresh_max_age).as_millis(), }; let refresh_token_enc = serde_json::to_string(&refresh_token)?; @@ -207,34 +238,37 @@ pub async fn update_user_identity( jar = jar.add(refresh_cookie); set_cookies(response_opts, jar); - Ok(delegate_identity(&identity)) + let namespaced_identity_key = generate_namespaced_identity_key(&namespace, identity_key); + let namespaced_identity = Secp256k1Identity::from_private_key(namespaced_identity_key); + + Ok(delegate_identity(&namespaced_identity)) } -pub async fn extract_or_generate_identity_impl() -> Result { +pub async fn extract_or_generate_identity_impl(namespace: String) -> Result { let key: Key = expect_context(); let jar: SignedCookieJar = extract_with_state(&key).await?; let kv: KVStoreImpl = expect_context(); - let base_identity = if let Some(identity) = try_extract_identity(&jar, &kv).await? { - Secp256k1Identity::from_private_key(identity) + let base_identity_key = if let Some(identity) = try_extract_identity(&jar, &kv).await? { + identity } else { - generate_and_save_identity(&kv).await? + generate_and_save_identity_key(&kv).await? }; let resp: ResponseOptions = expect_context(); - let delegated = update_user_identity(&resp, jar, base_identity).await?; + let delegated = set_cookie_and_get_namespaced_identity(&resp, jar, base_identity_key, &namespace).await?; Ok(delegated) } -pub async fn logout_identity_impl() -> Result { +pub async fn logout_identity_impl(namespace: String) -> Result { let key: Key = expect_context(); let kv: KVStoreImpl = expect_context(); let jar: SignedCookieJar = extract_with_state(&key).await?; - let base_identity = generate_and_save_identity(&kv).await?; + let base_identity_key = generate_and_save_identity_key(&kv).await?; let resp: ResponseOptions = expect_context(); - let delegated = update_user_identity(&resp, jar, base_identity).await?; + let delegated = set_cookie_and_get_namespaced_identity(&resp, jar, base_identity_key, &namespace).await?; Ok(delegated) } @@ -257,13 +291,16 @@ pub async fn upgrade_refresh_claim_impl( .ok_or_else(|| ServerFnError::new("No referrer host"))? .to_owned(); - let principal = verify_refresh_claim(s_claim, host, &key)?; - let sk = fetch_identity_from_kv(&kv, principal) + let principal = verify_refresh_claim(s_claim.clone(), host, &key)?; + let sk = fetch_identity_key_from_kv(&kv, principal) .await? .ok_or_else(|| ServerFnError::new("No identity found"))?; + + let namespace = s_claim.claim.namespace.clone(); + let delegated = - update_user_identity(&resp, jar, Secp256k1Identity::from_private_key(sk)).await?; + set_cookie_and_get_namespaced_identity(&resp, jar, sk, &namespace).await?; Ok(delegated) } diff --git a/types/src/lib.rs b/types/src/lib.rs index 6ae4b3b..f429c50 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -60,6 +60,7 @@ impl From for Message { pub struct RefreshTokenClaim { pub principal: Principal, pub expiry_epoch: Duration, + pub namespace: String, pub referrer_host: Host, } From edfc94ec133071053cec77860006b9a70d4d33f2 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Tue, 6 Aug 2024 22:37:33 +0530 Subject: [PATCH 03/10] fix google login for yral-auth client --- client/src/lib.rs | 5 +++-- ssr/src/api/server_impl/google.rs | 3 ++- ssr/src/api/server_impl/mod.rs | 1 + ssr/src/page/root.rs | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 5a4c11d..451c86e 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -110,7 +110,7 @@ impl AuthClient { .await } - pub fn prepare_auth_url(&self, identity: &impl Identity, host: url::Host) -> Result { + pub fn prepare_auth_url(&self, identity: &impl Identity, namespace: &str, host: url::Host) -> Result { let intent = LoginIntent { referrer_host: host, }; @@ -121,7 +121,8 @@ impl AuthClient { root_url .query_pairs_mut() .append_pair("principal", &principal.to_text()) - .append_pair("signature_json", &signature_json); + .append_pair("signature_json", &signature_json) + .append_pair("namespace", namespace); Ok(root_url) } diff --git a/ssr/src/api/server_impl/google.rs b/ssr/src/api/server_impl/google.rs index 8336d63..2c8097f 100644 --- a/ssr/src/api/server_impl/google.rs +++ b/ssr/src/api/server_impl/google.rs @@ -138,6 +138,7 @@ pub async fn perform_google_auth_impl( .ok_or_else(|| ServerFnError::new("Attempting google login without a temp identity"))?; let temp_id: TempIdentity = serde_json::from_str(temp_id_cookie.value())?; let principal = temp_id.principal; + let namespace = temp_id.namespace.clone(); let host = temp_id.referrer_host.clone(); temp_id.validate()?; @@ -147,7 +148,7 @@ pub async fn perform_google_auth_impl( } else { associate_principal_with_google_sub(&kv, principal, sub_id).await? }; - let claim = refresh_claim(principal, host); + let claim = refresh_claim(principal, host, namespace); let s_claim = sign_refresh_claim(claim, &key)?; Ok(s_claim) diff --git a/ssr/src/api/server_impl/mod.rs b/ssr/src/api/server_impl/mod.rs index c7a9a69..46df1be 100644 --- a/ssr/src/api/server_impl/mod.rs +++ b/ssr/src/api/server_impl/mod.rs @@ -60,6 +60,7 @@ pub fn delegate_identity(from: &impl Identity) -> DelegatedIdentityWire { #[derive(Serialize, Deserialize, Clone)] pub struct TempIdentity { pub principal: Principal, + pub namespace: String, pub signature: Signature, pub referrer_host: url::Host, } diff --git a/ssr/src/page/root.rs b/ssr/src/page/root.rs index 1925bae..2a16d29 100644 --- a/ssr/src/page/root.rs +++ b/ssr/src/page/root.rs @@ -34,6 +34,7 @@ async fn prepare_cookies(params: RootParams) -> Result<(), ServerFnError> { principal: params.principal, signature: sig, referrer_host, + namespace: params.namespace }; let temp_id_raw = serde_json::to_string(&temp_id)?; let temp_id_cookie = Cookie::build((TEMP_IDENTITY_COOKIE, temp_id_raw)) @@ -55,7 +56,8 @@ async fn prepare_cookies(params: RootParams) -> Result<(), ServerFnError> { struct RootParams { principal: Principal, /// Signature over [types::LoginIntent] - signature_json: String, + namespace: String, + signature_json: String } #[component] From 8a4bfc432c3cc70d99f29da49567efb8cf3ff8f8 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Tue, 6 Aug 2024 22:42:58 +0530 Subject: [PATCH 04/10] keep the identity same for yral namespace --- ssr/src/api/server_impl/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ssr/src/api/server_impl/mod.rs b/ssr/src/api/server_impl/mod.rs index 46df1be..740125c 100644 --- a/ssr/src/api/server_impl/mod.rs +++ b/ssr/src/api/server_impl/mod.rs @@ -194,6 +194,11 @@ async fn save_identity_in_kv(kv: &KVStoreImpl, identity_key: SecretKey) -> SecretKey{ + + if namespace.to_uppercase().eq("YRAL") { + return from_secret_key; + } + let app_name = namespace.as_bytes(); let mut combined_bytes:Vec = Vec::new(); From 2409977bd73f40bda9726520262a95917814b924 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Tue, 6 Aug 2024 22:55:04 +0530 Subject: [PATCH 05/10] move namespace to const --- client/src/lib.rs | 3 +-- ssr/src/api/mod.rs | 6 ++++-- ssr/src/consts.rs | 1 + ssr/src/page/root.rs | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 451c86e..beca540 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -121,8 +121,7 @@ impl AuthClient { root_url .query_pairs_mut() .append_pair("principal", &principal.to_text()) - .append_pair("signature_json", &signature_json) - .append_pair("namespace", namespace); + .append_pair("signature_json", &signature_json); Ok(root_url) } diff --git a/ssr/src/api/mod.rs b/ssr/src/api/mod.rs index 0130b66..be1fce1 100644 --- a/ssr/src/api/mod.rs +++ b/ssr/src/api/mod.rs @@ -4,17 +4,19 @@ use types::{ DelegatedIdentityWire, SignedRefreshTokenClaim, }; +use crate::consts::NAMESPACE; + #[cfg(feature = "ssr")] pub mod server_impl; #[server(endpoint = "extract_or_generate", input = Json, output = Json)] pub async fn extract_or_generate_identity() -> Result { - server_impl::extract_or_generate_identity_impl("yral".into()).await + server_impl::extract_or_generate_identity_impl(NAMESPACE.into()).await } #[server(endpoint = "logout", input = Json, output = Json)] pub async fn logout_identity() -> Result { - server_impl::logout_identity_impl("yral".into()).await + server_impl::logout_identity_impl(NAMESPACE.into()).await } #[server(endpoint = "upgrade_refresh_claim", input = Json, output = Json)] diff --git a/ssr/src/consts.rs b/ssr/src/consts.rs index 95ffea5..5fdcbf4 100644 --- a/ssr/src/consts.rs +++ b/ssr/src/consts.rs @@ -6,6 +6,7 @@ pub const DELEGATION_MAX_AGE: Duration = Duration::from_secs(60 * 60 * 24 * 7); pub const REFRESH_MAX_AGE: Duration = Duration::from_secs(60 * 60 * 24 * 30); pub const REFRESH_TOKEN_COOKIE: &str = "user-identity"; pub const TEMP_IDENTITY_COOKIE: &str = "temp-identity"; +pub const NAMESPACE: &str = "YRAL"; #[cfg(all(feature = "ssr", feature = "oauth-google"))] pub mod google { diff --git a/ssr/src/page/root.rs b/ssr/src/page/root.rs index 2a16d29..19c9581 100644 --- a/ssr/src/page/root.rs +++ b/ssr/src/page/root.rs @@ -3,6 +3,8 @@ use leptos::*; use leptos_router::*; use serde::{Deserialize, Serialize}; +use crate::consts::NAMESPACE; + #[server] async fn prepare_cookies(params: RootParams) -> Result<(), ServerFnError> { use crate::api::server_impl::{set_cookies, TempIdentity}; @@ -18,6 +20,7 @@ async fn prepare_cookies(params: RootParams) -> Result<(), ServerFnError> { use yral_identity::Signature; let sig: Signature = serde_json::from_str(¶ms.signature_json)?; + let namespace = NAMESPACE.into(); let headers: HeaderMap = extract().await?; let referrer_raw = headers @@ -34,7 +37,7 @@ async fn prepare_cookies(params: RootParams) -> Result<(), ServerFnError> { principal: params.principal, signature: sig, referrer_host, - namespace: params.namespace + namespace }; let temp_id_raw = serde_json::to_string(&temp_id)?; let temp_id_cookie = Cookie::build((TEMP_IDENTITY_COOKIE, temp_id_raw)) @@ -56,7 +59,6 @@ async fn prepare_cookies(params: RootParams) -> Result<(), ServerFnError> { struct RootParams { principal: Principal, /// Signature over [types::LoginIntent] - namespace: String, signature_json: String } From 960efd1b418edf6fdf5c2a05a42ecfa012390011 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Tue, 6 Aug 2024 23:00:08 +0530 Subject: [PATCH 06/10] remove namespace param from prepare-auth-url command in yral-aut-client --- client/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index beca540..5a4c11d 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -110,7 +110,7 @@ impl AuthClient { .await } - pub fn prepare_auth_url(&self, identity: &impl Identity, namespace: &str, host: url::Host) -> Result { + pub fn prepare_auth_url(&self, identity: &impl Identity, host: url::Host) -> Result { let intent = LoginIntent { referrer_host: host, }; From bd0c68ae3b010f6e25d4fcd69837aa36fc63d0d1 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Mon, 26 Aug 2024 22:54:52 +0530 Subject: [PATCH 07/10] added jwt authorization --- Cargo.lock | 51 +++++++++++++++++++++++++++++++++---------- client/src/lib.rs | 8 ++++--- ssr/Cargo.toml | 3 ++- ssr/src/api/mod.rs | 9 +++++++- ssr/src/extractors.rs | 39 +++++++++++++++++++++++++++++++++ ssr/src/lib.rs | 3 +++ 6 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 ssr/src/extractors.rs diff --git a/Cargo.lock b/Cargo.lock index 9202192..aff09b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1946,7 +1946,7 @@ dependencies = [ "k256", "lazy_static", "num-bigint", - "pem", + "pem 1.1.1", "rand", "simple_asn1", "zeroize", @@ -1960,7 +1960,7 @@ dependencies = [ "lazy_static", "num-bigint", "p256", - "pem", + "pem 1.1.1", "rand", "rand_chacha", "simple_asn1", @@ -2753,6 +2753,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem 3.0.4", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" version = "0.13.3" @@ -3522,6 +3537,16 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.0", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -5257,19 +5282,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -5294,9 +5320,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5304,9 +5330,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -5317,9 +5343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" @@ -5606,6 +5632,7 @@ dependencies = [ "ic-agent", "icondata", "icondata_core", + "jsonwebtoken", "k256", "leptos", "leptos_axum", diff --git a/client/src/lib.rs b/client/src/lib.rs index 5a4c11d..dff4e74 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -7,7 +7,7 @@ use consts::DEFAULT_AUTH_URL; pub use error::*; use ic_agent::{export::Principal, Identity}; use reqs::{EmptyReq, GetUserMetadataReqW, SetUserMetadataReqW, UpgradeRefreshClaimReq}; -use reqwest::Url; +use reqwest::{header::{self, HeaderMap}, Url}; use serde::{de::DeserializeOwned, Serialize}; use types::{ metadata::{ @@ -34,10 +34,12 @@ impl Default for AuthClient { } impl AuthClient { - pub fn with_base_url(base_url: Url) -> Self { + pub fn with_base_url(base_url: Url, token: &str) -> Self { + let headers = HeaderMap::new(); + headers.insert(header::AUTHORIZATION, format!("Bearer {token}")); Self { base_url, - client: Default::default(), + client: reqwest::Client::builder().default_headers(headers), } } diff --git a/ssr/Cargo.toml b/ssr/Cargo.toml index 732a00a..987dc40 100644 --- a/ssr/Cargo.toml +++ b/ssr/Cargo.toml @@ -16,7 +16,7 @@ leptos_router = { version = "0.6", features = ["nightly"] } tokio = { workspace = true, features = ["rt-multi-thread"], optional = true } tower = { version = "0.4", optional = true } tower-http = { version = "0.5", features = ["fs", "cors"], optional = true } -wasm-bindgen = "=0.2.92" +wasm-bindgen = "=0.2.93" thiserror = "1" tracing = { version = "0.1", optional = true } http = "1" @@ -40,6 +40,7 @@ icondata_core = "0.1.0" url = "2.5.0" hmac = { version = "0.12.1", optional = true } rand = { version = "0.8.5", features = ["std_rng"] } +jsonwebtoken = "9.3.0" [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate", "ic-agent/wasm-bindgen"] diff --git a/ssr/src/api/mod.rs b/ssr/src/api/mod.rs index be1fce1..06727a1 100644 --- a/ssr/src/api/mod.rs +++ b/ssr/src/api/mod.rs @@ -6,12 +6,19 @@ use types::{ use crate::consts::NAMESPACE; +#[cfg(feature = "ssr")] +use crate::extractors::JwtAuth; + #[cfg(feature = "ssr")] pub mod server_impl; +#[cfg(feature = "ssr")] +use leptos_axum::extract; + #[server(endpoint = "extract_or_generate", input = Json, output = Json)] pub async fn extract_or_generate_identity() -> Result { - server_impl::extract_or_generate_identity_impl(NAMESPACE.into()).await + let jwt_auth = extract::().await?; + server_impl::extract_or_generate_identity_impl(jwt_auth.namespace).await } #[server(endpoint = "logout", input = Json, output = Json)] diff --git a/ssr/src/extractors.rs b/ssr/src/extractors.rs new file mode 100644 index 0000000..bb9ce8e --- /dev/null +++ b/ssr/src/extractors.rs @@ -0,0 +1,39 @@ +use std::env; + +use axum::{async_trait, extract::FromRequestParts}; +use http::{header, request::Parts}; +use jsonwebtoken::{decode, DecodingKey, Validation}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct JwtAuth { + pub namespace: String, +} + +pub fn decode_jwt_token(token: &str) -> Result{ + let pub_identity = env::var("YRAL_PUBLIC_KEY").expect("Yral public key should be present"); + let decoding_key = DecodingKey::from_ec_pem(pub_identity.as_bytes()).map_err(|e| e.to_string())?; + + let token_data = decode::(token, &decoding_key, &Validation::new(jsonwebtoken::Algorithm::ES256)); + match token_data { + Ok(data) => Ok(data.claims), + Err(e) => Err(e.to_string()) + } +} + + + +#[async_trait] +impl FromRequestParts<()> for JwtAuth { + type Rejection = String; + + async fn from_request_parts(parts: &mut Parts, _: &()) -> Result{ + let access_token = parts.headers.get(header::AUTHORIZATION).and_then(|val| val.to_str().ok()).and_then(|str| str.split(" ").nth(1)); + match access_token { + Some(token) => { + decode_jwt_token(token) + }, + None => Err("Unauthorized".into()) + } + } +} \ No newline at end of file diff --git a/ssr/src/lib.rs b/ssr/src/lib.rs index 2749969..3713c4d 100644 --- a/ssr/src/lib.rs +++ b/ssr/src/lib.rs @@ -5,6 +5,9 @@ pub mod consts; pub mod error_template; #[cfg(feature = "ssr")] pub mod fileserv; + +#[cfg(feature = "ssr")] +pub mod extractors; pub mod page; pub mod state; From c338cc78f98133f46ef0682f3f55e6686ffcd041 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Mon, 26 Aug 2024 23:02:11 +0530 Subject: [PATCH 08/10] update client --- client/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index dff4e74..d1a8b23 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -35,11 +35,11 @@ impl Default for AuthClient { impl AuthClient { pub fn with_base_url(base_url: Url, token: &str) -> Self { - let headers = HeaderMap::new(); + let headers = HeaderMap::::new(); headers.insert(header::AUTHORIZATION, format!("Bearer {token}")); Self { base_url, - client: reqwest::Client::builder().default_headers(headers), + client: reqwest::Client::builder().default_headers(headers).build().unwrap(), } } From f47d8e3f35078f1fcf39740ceb61f77aa19d2719 Mon Sep 17 00:00:00 2001 From: ravibazz Date: Mon, 26 Aug 2024 23:06:54 +0530 Subject: [PATCH 09/10] add jwt default header in client --- client/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index d1a8b23..8768183 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -35,8 +35,8 @@ impl Default for AuthClient { impl AuthClient { pub fn with_base_url(base_url: Url, token: &str) -> Self { - let headers = HeaderMap::::new(); - headers.insert(header::AUTHORIZATION, format!("Bearer {token}")); + let mut headers = HeaderMap::new(); + headers.insert(header::AUTHORIZATION, format!("Bearer {token}").parse().unwrap()); Self { base_url, client: reqwest::Client::builder().default_headers(headers).build().unwrap(), From d2cbfc67be08d17596571d454d96cae1abc3b2ed Mon Sep 17 00:00:00 2001 From: ravibazz Date: Thu, 5 Sep 2024 14:01:15 +0530 Subject: [PATCH 10/10] token should be optional for metadata clients --- client/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 8768183..cce5da7 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -34,9 +34,11 @@ impl Default for AuthClient { } impl AuthClient { - pub fn with_base_url(base_url: Url, token: &str) -> Self { + pub fn with_base_url(base_url: Url, opt_token: Option<&str>) -> Self { let mut headers = HeaderMap::new(); - headers.insert(header::AUTHORIZATION, format!("Bearer {token}").parse().unwrap()); + if let Some(token) = opt_token { + headers.insert(header::AUTHORIZATION, format!("Bearer {token}").parse().unwrap()); + } Self { base_url, client: reqwest::Client::builder().default_headers(headers).build().unwrap(),