Skip to content

Commit

Permalink
Shield: Add user extractor
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielleHuisman committed Dec 28, 2024
1 parent 74762aa commit 148c810
Show file tree
Hide file tree
Showing 23 changed files with 298 additions and 36 deletions.
46 changes: 45 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ tokio = "1.42.0"
tower-layer = "0.3.3"
tower-service = "0.3.3"
tower-sessions = "0.13.0"
typetag = "0.2.19"
uuid = "1.11.0"
wasm-bindgen = "0.2.97"
19 changes: 6 additions & 13 deletions examples/leptos-actix/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use leptos::prelude::*;
use leptos_meta::{provide_meta_context, MetaTags, Title};
use leptos_router::{
components::{Route, Router, Routes, A},
components::{Route, Router, Routes},
path,
};
use shield_leptos::routes::SignIn;
use shield_leptos::routes::{SignIn, SignOut};

use crate::home::HomePage;

pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
Expand Down Expand Up @@ -34,21 +36,12 @@ pub fn App() -> impl IntoView {
<Router>
<main>
<Routes fallback=|| "Not found.".into_view()>
<Route path=path!("") view=HomePage/>
<Route path=path!("") view=HomePage />

<Route path=path!("/auth/sign-in") view=SignIn />
<Route path=path!("/auth/sign-out") view=SignOut />
</Routes>
</main>
</Router>
}
}

#[component]
fn HomePage() -> impl IntoView {
view! {
<h1>"Shield Leptos Actix Example"</h1>
<A href="/auth/sign-in">
<button>"Sign in"</button>
</A>
}
}
40 changes: 40 additions & 0 deletions examples/leptos-actix/src/home.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::sync::Arc;

use leptos::{either::Either, prelude::*};
use leptos_router::components::A;

#[server]
pub async fn user() -> Result<Option<Arc<dyn shield::User>>, ServerFnError> {
use shield_leptos::context::extract_user;

Ok(extract_user().await)
}

#[component]
pub fn HomePage() -> impl IntoView {
let user = OnceResource::new(user());

view! {
<h1>"Shield Leptos Actix Example"</h1>

<Suspense fallback=|| view! { "Loading..." }>
{move || Suspend::new(async move { match user.await {
Ok(user) => Either::Left(match user {
Some(_user) => Either::Left(view! {
<A href="/auth/sign-out">
<button>"Sign out"</button>
</A>
}),
None => Either::Right(view! {
<A href="/auth/sign-in">
<button>"Sign in"</button>
</A>
}),
}),
Err(err) => Either::Right(view! {
{err.to_string()}
})
}})}
</Suspense>
}
}
1 change: 1 addition & 0 deletions examples/leptos-actix/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod app;
mod home;

#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
Expand Down
19 changes: 6 additions & 13 deletions examples/leptos-axum/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use leptos::prelude::*;
use leptos_meta::{provide_meta_context, MetaTags, Title};
use leptos_router::{
components::{Route, Router, Routes, A},
components::{Route, Router, Routes},
path,
};
use shield_leptos::routes::SignIn;
use shield_leptos::routes::{SignIn, SignOut};

use crate::home::HomePage;

pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
Expand Down Expand Up @@ -34,21 +36,12 @@ pub fn App() -> impl IntoView {
<Router>
<main>
<Routes fallback=|| "Not found.".into_view()>
<Route path=path!("") view=HomePage/>
<Route path=path!("") view=HomePage />

<Route path=path!("/auth/sign-in") view=SignIn />
<Route path=path!("/auth/sign-out") view=SignOut />
</Routes>
</main>
</Router>
}
}

#[component]
fn HomePage() -> impl IntoView {
view! {
<h1>"Shield Leptos Axum Example"</h1>
<A href="/auth/sign-in">
<button>"Sign in"</button>
</A>
}
}
42 changes: 42 additions & 0 deletions examples/leptos-axum/src/home.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::sync::Arc;

use leptos::{either::Either, prelude::*};
use leptos_router::components::A;

#[server]
pub async fn user() -> Result<Option<Arc<dyn shield::User>>, ServerFnError> {
use shield_leptos::context::extract_user;

Ok(extract_user().await)
}

#[component]
pub fn HomePage() -> impl IntoView {
let user = OnceResource::new(user());

view! {
<h1>"Shield Leptos Axum Example"</h1>

<Suspense fallback=|| view! { "Loading..." }>
{move || Suspend::new(async move { match user.await {
Ok(user) => Either::Left(match user {
Some(user) => Either::Left(view! {
{user.id()}

<A href="/auth/sign-out">
<button>"Sign out"</button>
</A>
}),
None => Either::Right(view! {
<A href="/auth/sign-in">
<button>"Sign in"</button>
</A>
}),
}),
Err(err) => Either::Right(view! {
{err.to_string()}
})
}})}
</Suspense>
}
}
1 change: 1 addition & 0 deletions examples/leptos-axum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod app;
mod home;

#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
Expand Down
3 changes: 2 additions & 1 deletion packages/core/shield/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ version.workspace = true

[dependencies]
async-trait.workspace = true
chrono.workspace = true
chrono = { workspace = true, features = ["serde"] }
futures.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
typetag.workspace = true
9 changes: 7 additions & 2 deletions packages/core/shield/src/user.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[typetag::serde(tag = "type")]
pub trait User: Send + Sync {
fn id(&self) -> String;
}
Expand All @@ -15,7 +17,7 @@ pub struct UpdateUser {
pub name: Option<Option<String>>,
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmailAddress {
pub id: String,
pub email: String,
Expand Down Expand Up @@ -49,13 +51,16 @@ pub struct UpdateEmailAddress {

#[cfg(test)]
pub(crate) mod tests {
use serde::{Deserialize, Serialize};

use super::User;

#[derive(Clone, Debug)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TestUser {
id: String,
}

#[typetag::serde]
impl User for TestUser {
fn id(&self) -> String {
self.id.clone()
Expand Down
19 changes: 19 additions & 0 deletions packages/integrations/shield-actix/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,22 @@ impl FromRequest for ExtractSession {
)
}
}

pub struct ExtractUser<U: User>(pub Option<U>);

impl<U: User + Clone + 'static> FromRequest for ExtractUser<U> {
type Error = Error;
type Future = Ready<Result<Self, Self::Error>>;

fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
ready(
req.extensions()
.get::<Option<U>>()
.cloned()
.map(ExtractUser)
.ok_or(ErrorInternalServerError(
"Can't extract Shield user. Is `ShieldTransform` enabled?",
)),
)
}
}
19 changes: 19 additions & 0 deletions packages/integrations/shield-axum/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,22 @@ impl<S: Send + Sync> FromRequestParts<S> for ExtractSession {
))
}
}

pub struct ExtractUser<U: User>(pub Option<U>);

#[async_trait]
impl<S: Send + Sync, U: User + Clone + 'static> FromRequestParts<S> for ExtractUser<U> {
type Rejection = (StatusCode, &'static str);

async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
parts
.extensions
.get::<Option<U>>()
.cloned()
.map(ExtractUser)
.ok_or((
StatusCode::INTERNAL_SERVER_ERROR,
"Can't extract Shield user. Is `ShieldLayer` enabled?",
))
}
}
9 changes: 8 additions & 1 deletion packages/integrations/shield-leptos-actix/src/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use async_trait::async_trait;
use leptos::prelude::provide_context;
use leptos_actix::{extract, redirect};
use shield::{Session, ShieldDyn, User};
use shield_actix::{ExtractSession, ExtractShield};
use shield_actix::{ExtractSession, ExtractShield, ExtractUser};
use shield_leptos::LeptosIntegration;

pub struct LeptosActixIntegration<U: User>(PhantomData<U>);
Expand All @@ -25,9 +25,16 @@ impl<U: User + Clone + 'static> LeptosIntegration for LeptosActixIntegration<U>

async fn extract_session(&self) -> Session {
let ExtractSession(session) = extract().await.expect("TODO");

session
}

async fn extract_user(&self) -> Option<Arc<dyn User>> {
let ExtractUser(user) = extract::<ExtractUser<U>>().await.expect("TODO");

user.map(|user| Arc::new(user) as Arc<dyn User>)
}

fn redirect(&self, path: &str) {
redirect(path);
}
Expand Down
Loading

0 comments on commit 148c810

Please sign in to comment.