Skip to content

Commit

Permalink
get user from jwt stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
djkato committed Jan 17, 2025
1 parent bdcf1a7 commit d800039
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 42 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 45 additions & 33 deletions app-template-ui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct UrlAppParams {
#[component]
pub fn App() -> impl IntoView {
let (bridge_read, bridge_set) = create_signal::<Option<AppBridge>>(None);

let context = use_context::<AppState>();
create_effect(move |_| match AppBridge::new(true) {
Ok(bridge) => bridge_set(Some(bridge)),
Err(e) => console_error(&format!("{:?}", e)),
Expand All @@ -44,41 +44,29 @@ pub fn App() -> impl IntoView {
Event::Handshake(payload) => {
if let Some(mut bridge) = bridge_read.get_untracked() {
let payload2 = payload.clone();
let (tx, rx) = std::sync::mpsc::sync_channel(5);
spawn_local(async move {
match payload2.token_into_user().await {
Ok(user) => {
console_log(&format!(
"bridge user updated to:\n {:?}",
"setting bridge user to:\n {:?}",
&user
));
_ = tx.send(Some(user));
bridge.state.user = Some(user);
}
Err(e) => {
console_error(&format!(
"failed converting JWT into user data, {e:?}"
));
_ = tx.send(None);
bridge.state.user = None;
}
};
bridge.state.token = Some(payload.clone().token);
bridge.state.dashboard_version = payload.dashboard_version;
bridge.state.saleor_version = payload.saleor_version;
//if for some reason it unsets sometimes
bridge.state.ready = true;
bridge_set(Some(bridge));
});

let mut attempts = 0;
//TODO: God someone give me a better solution please. rx.recv() blocks
//and halts the whole wasm app
loop {
attempts += 1;
if let Ok(user) = rx.try_recv() {
bridge.state.user = user;
};
if attempts > 1000000 {
break;
}
}
bridge.state.token = Some(payload.clone().token);
bridge.state.dashboard_version = payload.dashboard_version;
bridge.state.saleor_version = payload.saleor_version;
bridge_set(Some(bridge))
}
}
Event::Response(_) => {
Expand Down Expand Up @@ -130,6 +118,19 @@ pub fn App() -> impl IntoView {
outside_errors.insert_with_default_key(AppError::NotFound);
view! { <ErrorTemplate outside_errors /> }.into_view()
}>
<header class="h-12">
<div class="h-full bg-default1 border-b-[1px] border-default1 px-4 py-2 flex justify-between items-center">
<h2 class="">
{ context.map_or("[Cool App]".to_owned(), |c| c.manifest.name)}
</h2>
<span class="">
{move || match bridge_read.get() {
Some(bridge) => bridge.state.user.map_or("[Loading bridge...]".into(), |u|"Welcome, ".to_owned()+ &u.email),
None => "[Not authenticated]".into()
}}
</span>
</div>
</header>
<main class="p-4 md:p-8 md:px-16">
<Routes>
<Route path="/" view=Home />
Expand All @@ -142,33 +143,44 @@ pub fn App() -> impl IntoView {
#[cfg(feature = "ssr")]
use saleor_app_sdk::settings_manager::metadata::MetadataSettingsManager;

#[cfg(feature = "ssr")]
#[derive(Debug, Clone, axum::extract::FromRef)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ssr", derive(axum::extract::FromRef))]
pub struct AppState {
pub saleor_app: std::sync::Arc<tokio::sync::Mutex<saleor_app_sdk::SaleorApp>>,
pub leptos_options: LeptosOptions,
pub config: saleor_app_sdk::config::Config,
pub manifest: saleor_app_sdk::manifest::AppManifest,
#[cfg(feature = "ssr")]
pub saleor_app: std::sync::Arc<tokio::sync::Mutex<saleor_app_sdk::SaleorApp>>,
#[cfg(feature = "ssr")]
pub settings: std::sync::Arc<
tokio::sync::Mutex<Option<MetadataSettingsManager<AppSettingsKey, AppSettings>>>,
>,
pub leptos_options: LeptosOptions,
}

#[cfg(feature = "ssr")]
#[derive(Hash, PartialEq, Eq, Debug, Clone, EnumString, strum_macros::Display)]
pub enum AppSettingsKey {
Locale,
ActiveFields,
Global,
//ID
User(String),
}

#[cfg(feature = "ssr")]
#[derive(Clone, Deserialize, Serialize, Debug)]
pub enum AppSettings {
Locale(LocaleCode),
ActiveFields(Vec<OrderDetailField>),
Global(GlobalAppSettings),
UserSettings(UserAppSettings),
}

#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct GlobalAppSettings {
idk: bool,
}

#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct UserAppSettings {
pub locale: Option<LocaleCode>,
pub active_pdf_fields: Vec<OrderDetailField>,
}

#[cfg(feature = "ssr")]
#[derive(Clone, Deserialize, Serialize, Debug)]
pub enum OrderDetailField {
VariantName,
Expand Down
7 changes: 3 additions & 4 deletions app-template-ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,15 @@ async fn main() -> Result<(), std::io::Error> {
use fileserv::file_and_error_handler;
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use saleor_app_sdk::manifest::{
extension::AppExtensionBuilder, AppExtensionMount, AppExtensionTarget,
};
use saleor_app_sdk::{
cargo_info,
config::Config,
manifest::{AppManifestBuilder, AppPermission},
SaleorApp,
};
use saleor_app_sdk::{
manifest::{extension::AppExtensionBuilder, AppExtensionMount, AppExtensionTarget},
settings_manager::metadata::MetadataSettingsManager,
};
use std::sync::Arc;
use tokio::sync::Mutex;

Expand Down
2 changes: 1 addition & 1 deletion app-template-ui/src/routes/extensions/order_to_pdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub fn OrderToPdf(bridge: ReadSignal<Option<AppBridge>>) -> impl IntoView {

{move || match bridge() {
Some(bridge) => {
match bridge.state.ready{
match bridge.state.ready {
true => view!{
<div>
<button on:click=move |_|{
Expand Down
5 changes: 4 additions & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "saleor-app-sdk"
authors = ["Djkáťo <djkatovfx@gmail.com>"]
version = "0.6.2"
version = "0.6.4"
edition = "2021"
description = "Unofficial Saleor App SDK library, made to for Rust."
keywords = ["saleor", "sdk", "plugin"]
Expand All @@ -22,6 +22,7 @@ iso_currency = { workspace = true, features = ["with-serde", "iterator"] }
strum.workspace = true
strum_macros.workspace = true
async-trait = { version = "0.1.80" }
base64 = { optional = true, version = "0.22.1" }

## Needed for middleware
axum = { workspace = true, optional = true }
Expand Down Expand Up @@ -94,4 +95,6 @@ bridge = [
"dep:web-sys",
"dep:jsonwebtoken",
"dep:reqwest",
"dep:base64",
"dep:url",
]
25 changes: 23 additions & 2 deletions sdk/src/bridge/event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

Check failure on line 1 in sdk/src/bridge/event.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `std::str::FromStr`

Check warning on line 1 in sdk/src/bridge/event.rs

View workflow job for this annotation

GitHub Actions / Check

unused import: `std::str::FromStr`

use super::{AppBridgeUser, ThemeType};
use crate::manifest::{AppPermission, LocaleCode};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -59,19 +61,38 @@ pub enum TokenIntoUserError {
SerdeJsonError(#[from] serde_json::Error),
#[error("Failed validating or parsing JWT, {0}")]
CryptoError(#[from] jsonwebtoken::errors::Error),
#[error("missing member in public key")]
#[error("Failed converting issuer to url, {0}")]
UrlError(#[from] url::ParseError),
#[error("missing member in public key or wrong JWK format, can't separate into 3 parts with split('.')")]
MissingKeyField,
}

impl PayloadHanshake {
pub async fn token_into_user(&self) -> Result<AppBridgeUser, TokenIntoUserError> {
use base64::prelude::*;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde_json::Value;

//godda get the issuer (`iss` field) out of the JWT beforehand, rip
let data_part = self
.token
.split(".")
.nth(1)
.ok_or(TokenIntoUserError::MissingKeyField)?
.to_owned();

let decoded_user: AppBridgeUser =
serde_json::from_slice(&BASE64_URL_SAFE_NO_PAD.decode(data_part).unwrap()).unwrap();

let mut url = url::Url::parse(&decoded_user.iss)?;
url.set_path(".well-known/jwks.json");

//now fetch remote stuff and validate along the way
let jwks: Value = {
let get_res = reqwest::get("http://localhost:8000/.well-known/jwks.json").await?;
let get_res = reqwest::get(url).await?;
get_res.json::<Value>().await?
};

let nstr = jwks["keys"][0]["n"]
.as_str()
.ok_or(TokenIntoUserError::MissingKeyField)?;
Expand Down

0 comments on commit d800039

Please sign in to comment.