Update openid example (#2474)

* Update openid example

* Trim unnecessary deps

* Update serve instructions

* Remove unnecessary version in patch directive

* Remove outdated workspace instructions

* use storage signals instead of global signals with manual synchronization

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
Sam TayandEvan Almloff authored and GitHub committed 2024-06-06 18:05:25 -07:00
1 parent 5494e38cf8
commit 054351139f
10 files changed
+6623 -306

No files matched your search

@@ -0,0 +1,5 @@
[env]
DIOXUS_FRONT_ISSUER_URL = "TODO"
DIOXUS_FRONT_CLIENT_ID = "TODO"
DIOXUS_FRONT_CLIENT_SECRET = "TODO"
DIOXUS_FRONT_URL = "http://localhost:8080"
File diff suppressed because it is too large. Load diff
+25 -14
View File
@@ -7,19 +7,30 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.86"
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
dioxus-logger = "0.4.1" dioxus = { path = "../../packages/dioxus", default_features = true, features = [
dioxus = { path = "../../packages/dioxus", version = "*" } "router",
dioxus-router = { path = "../../packages/router", version = "*" } "signals",
dioxus-web = { path = "../../packages/web", version = "*" } ], version = "*" }
fermi = { path = "../../packages/fermi", version = "*" } dioxus-logger = "0.5.1"
form_urlencoded = "1.2.0" dioxus-sdk = { git = "https://github.com/Dioxuslabs/sdk", features = [
gloo-storage = "0.3.0" "storage",
] }
form_urlencoded = "1.2.1"
log = "0.4" log = "0.4"
openidconnect = "3.4.0" openidconnect = "3.5.0"
reqwest = "0.11.20" serde = { version = "1.0.203", features = ["derive"] }
serde = { version = "1.0.188", features = ["derive"] } uuid = "1.8"
serde_json = "1.0.105"
thiserror = "1.0.48" [features]
uuid = "1.4" default = ["web"]
web-sys = { version = "0.3", features = ["Request", "Document"] } server = ["dioxus/axum"]
web = ["dioxus/web"]
desktop = ["dioxus/desktop"]
fullstack = ["dioxus/fullstack"]
# since we're using dioxus from local path, inform dioxus-sdk to use it as well
[patch.crates-io]
dioxus = { path = "../../packages/dioxus" }
dioxus-signals = { path = "../../packages/signals" }
+6 -7
View File
@@ -1,13 +1,12 @@
# OpenID Connect example to show how to authenticate an user # OpenID Connect example to show how to authenticate an user
The environment variables in `.cargo/config.toml` must be set in order for this example to work(if this example is just being compiled from the root workspace, the `.cargo/config.toml` from the root workspace must be set as stated in the [Cargo book](https://doc.rust-lang.org/cargo/reference/config.html)). The environment variables in [`.cargo/config.toml`](./.cargo/config.toml) must be set in order for this example to work.
Once they are set, you can run `dx serve` Once they are set, you can run `dx serve --platform web` or `dx serve --platform desktop`.
### Environment variables summary ### Environment variables summary
```DIOXUS_FRONT_ISSUER_URL``` The openid-connect's issuer url - `DIOXUS_FRONT_ISSUER_URL`: The openid-connect's issuer url
- `DIOXUS_FRONT_CLIENT_ID`: The openid-connect's client id
```DIOXUS_FRONT_CLIENT_ID``` The openid-connect's client id - `DIOXUS_FRONT_CLIENT_SECRET`: The openid-connect's client secret
- `DIOXUS_FRONT_URL`: The url the frontend is supposed to be running on, it could be for example `http://localhost:8080`
```DIOXUS_FRONT_URL``` The url the frontend is supposed to be running on, it could be for example `http://localhost:8080`
@@ -1,20 +0,0 @@
use openidconnect::{core::CoreErrorResponseType, url, RequestTokenError, StandardErrorResponse};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Discovery error: {0}")]
OpenIdConnect(
#[from] openidconnect::DiscoveryError<openidconnect::reqwest::Error<reqwest::Error>>,
),
#[error("Parsing error: {0}")]
Parse(#[from] url::ParseError),
#[error("Request token error: {0}")]
RequestToken(
#[from]
RequestTokenError<
openidconnect::reqwest::Error<reqwest::Error>,
StandardErrorResponse<CoreErrorResponseType>,
>,
),
}
+13 -35
View File
@@ -1,58 +1,36 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use fermi::*; use dioxus_logger::tracing::Level;
use gloo_storage::{LocalStorage, Storage}; use router::Route;
use log::LevelFilter;
use crate::oidc::ClientState;
use crate::storage::{use_auth_request_provider, use_auth_token_provider};
pub(crate) mod constants; pub(crate) mod constants;
pub(crate) mod errors;
pub(crate) mod model; pub(crate) mod model;
pub(crate) mod oidc; pub(crate) mod oidc;
pub(crate) mod props; pub(crate) mod props;
pub(crate) mod router; pub(crate) mod router;
pub(crate) mod storage; pub(crate) mod storage;
pub(crate) mod views; pub(crate) mod views;
use oidc::{AuthRequestState, AuthTokenState};
use router::Route;
use crate::{ pub static CLIENT: GlobalSignal<ClientState> = Signal::global(ClientState::default);
constants::{DIOXUS_FRONT_AUTH_REQUEST, DIOXUS_FRONT_AUTH_TOKEN},
oidc::ClientState,
};
pub static FERMI_CLIENT: fermi::AtomRef<ClientState> = AtomRef(|_| ClientState::default());
// An option is required to prevent the component from being constantly refreshed
pub static FERMI_AUTH_TOKEN: fermi::AtomRef<Option<AuthTokenState>> = AtomRef(|_| None);
pub static FERMI_AUTH_REQUEST: fermi::AtomRef<Option<AuthRequestState>> = AtomRef(|_| None);
pub static DIOXUS_FRONT_ISSUER_URL: &str = env!("DIOXUS_FRONT_ISSUER_URL"); pub static DIOXUS_FRONT_ISSUER_URL: &str = env!("DIOXUS_FRONT_ISSUER_URL");
pub static DIOXUS_FRONT_CLIENT_ID: &str = env!("DIOXUS_FRONT_CLIENT_ID"); pub static DIOXUS_FRONT_CLIENT_ID: &str = env!("DIOXUS_FRONT_CLIENT_ID");
pub static DIOXUS_FRONT_CLIENT_SECRET: &str = env!("DIOXUS_FRONT_CLIENT_SECRET");
pub static DIOXUS_FRONT_URL: &str = env!("DIOXUS_FRONT_URL"); pub static DIOXUS_FRONT_URL: &str = env!("DIOXUS_FRONT_URL");
fn App() -> Element { fn App() -> Element {
use_init_atom_root(cx); use_auth_request_provider();
use_auth_token_provider();
// Retrieve the value stored in the browser's storage
let stored_auth_token = LocalStorage::get(DIOXUS_FRONT_AUTH_TOKEN)
.ok()
.unwrap_or(AuthTokenState::default());
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN);
if fermi_auth_token.read().is_none() {
*fermi_auth_token.write() = Some(stored_auth_token);
}
let stored_auth_request = LocalStorage::get(DIOXUS_FRONT_AUTH_REQUEST)
.ok()
.unwrap_or(AuthRequestState::default());
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST);
if fermi_auth_request.read().is_none() {
*fermi_auth_request.write() = Some(stored_auth_request);
}
rsx! { Router::<Route> {} } rsx! { Router::<Route> {} }
} }
fn main() { fn main() {
dioxus_logger::init(LevelFilter::Info).expect("failed to init logger"); dioxus_logger::init(Level::DEBUG).expect("failed to init logger");
dioxus_sdk::set_dir!();
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
log::info!("starting app"); log::info!("starting app");
dioxus_web::launch(App); launch(App);
} }
+25 -23
View File
@@ -1,10 +1,11 @@
use anyhow::Result;
use openidconnect::{ use openidconnect::{
core::{CoreClient, CoreErrorResponseType, CoreIdToken, CoreResponseType, CoreTokenResponse}, core::{CoreClient, CoreIdToken, CoreResponseType, CoreTokenResponse},
reqwest::async_http_client, reqwest::async_http_client,
url::Url, url::Url,
AuthenticationFlow, AuthorizationCode, ClaimsVerificationError, ClientId, CsrfToken, IssuerUrl, AuthenticationFlow, AuthorizationCode, ClaimsVerificationError, ClientId, ClientSecret,
LogoutRequest, Nonce, ProviderMetadataWithLogout, RedirectUrl, RefreshToken, RequestTokenError, CsrfToken, IssuerUrl, LogoutRequest, Nonce, ProviderMetadataWithLogout, RedirectUrl,
StandardErrorResponse, RefreshToken,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -16,12 +17,12 @@ pub struct ClientState {
} }
/// State that holds the nonce and authorization url and the nonce generated to log in an user /// State that holds the nonce and authorization url and the nonce generated to log in an user
#[derive(Clone, Deserialize, Serialize, Default)] #[derive(Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct AuthRequestState { pub struct AuthRequestState {
pub auth_request: Option<AuthRequest>, pub auth_request: Option<AuthRequest>,
} }
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, PartialEq, Deserialize, Serialize)]
pub struct AuthRequest { pub struct AuthRequest {
pub nonce: Nonce, pub nonce: Nonce,
pub authorize_url: String, pub authorize_url: String,
@@ -36,6 +37,14 @@ pub struct AuthTokenState {
pub refresh_token: Option<RefreshToken>, pub refresh_token: Option<RefreshToken>,
} }
impl PartialEq for AuthTokenState {
fn eq(&self, other: &Self) -> bool {
self.id_token == other.id_token
&& self.refresh_token.as_ref().map(|t| t.secret().clone())
== other.refresh_token.as_ref().map(|t| t.secret().clone())
}
}
pub fn email( pub fn email(
client: CoreClient, client: CoreClient,
id_token: CoreIdToken, id_token: CoreIdToken,
@@ -63,15 +72,17 @@ pub fn authorize_url(client: CoreClient) -> AuthRequest {
} }
} }
pub async fn init_provider_metadata() -> Result<ProviderMetadataWithLogout, crate::errors::Error> { pub async fn init_provider_metadata() -> Result<ProviderMetadataWithLogout> {
let issuer_url = IssuerUrl::new(crate::DIOXUS_FRONT_ISSUER_URL.to_string())?; let issuer_url = IssuerUrl::new(crate::DIOXUS_FRONT_ISSUER_URL.to_string())?;
Ok(ProviderMetadataWithLogout::discover_async(issuer_url, async_http_client).await?) Ok(ProviderMetadataWithLogout::discover_async(issuer_url, async_http_client).await?)
} }
pub async fn init_oidc_client() -> Result<(ClientId, CoreClient), crate::errors::Error> { pub async fn init_oidc_client() -> Result<(ClientId, CoreClient)> {
let client_id = ClientId::new(crate::DIOXUS_FRONT_CLIENT_ID.to_string()); let client_id = ClientId::new(crate::DIOXUS_FRONT_CLIENT_ID.to_string());
let provider_metadata = init_provider_metadata().await?; let provider_metadata = init_provider_metadata().await?;
let client_secret = None; let client_secret = Some(ClientSecret::new(
crate::DIOXUS_FRONT_CLIENT_SECRET.to_string(),
));
let redirect_url = RedirectUrl::new(format!("{}/login", crate::DIOXUS_FRONT_URL))?; let redirect_url = RedirectUrl::new(format!("{}/login", crate::DIOXUS_FRONT_URL))?;
Ok(( Ok((
@@ -82,10 +93,7 @@ pub async fn init_oidc_client() -> Result<(ClientId, CoreClient), crate::errors:
} }
///TODO: Add pkce_pacifier ///TODO: Add pkce_pacifier
pub async fn token_response( pub async fn token_response(oidc_client: CoreClient, code: String) -> Result<CoreTokenResponse> {
oidc_client: CoreClient,
code: String,
) -> Result<CoreTokenResponse, crate::errors::Error> {
// let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); // let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
Ok(oidc_client Ok(oidc_client
.exchange_code(AuthorizationCode::new(code.clone())) .exchange_code(AuthorizationCode::new(code.clone()))
@@ -97,20 +105,14 @@ pub async fn token_response(
pub async fn exchange_refresh_token( pub async fn exchange_refresh_token(
oidc_client: CoreClient, oidc_client: CoreClient,
refresh_token: RefreshToken, refresh_token: RefreshToken,
) -> Result< ) -> Result<CoreTokenResponse> {
CoreTokenResponse, Ok(oidc_client
RequestTokenError<
openidconnect::reqwest::Error<reqwest::Error>,
StandardErrorResponse<CoreErrorResponseType>,
>,
> {
oidc_client
.exchange_refresh_token(&refresh_token) .exchange_refresh_token(&refresh_token)
.request_async(async_http_client) .request_async(async_http_client)
.await .await?)
} }
pub async fn log_out_url(id_token_hint: CoreIdToken) -> Result<Url, crate::errors::Error> { pub async fn log_out_url(id_token_hint: CoreIdToken) -> Result<Url> {
let provider_metadata = init_provider_metadata().await?; let provider_metadata = init_provider_metadata().await?;
let end_session_url = provider_metadata let end_session_url = provider_metadata
.additional_metadata() .additional_metadata()
+22 -25
View File
@@ -1,38 +1,35 @@
use fermi::UseAtomRef; use dioxus::prelude::*;
use gloo_storage::{LocalStorage, Storage}; use dioxus_sdk::storage::*;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
constants::{DIOXUS_FRONT_AUTH_REQUEST, DIOXUS_FRONT_AUTH_TOKEN}, constants::{DIOXUS_FRONT_AUTH_REQUEST, DIOXUS_FRONT_AUTH_TOKEN},
oidc::{AuthRequestState, AuthTokenState}, oidc::{AuthRequestState, AuthTokenState},
}; };
#[derive(Serialize, Deserialize, Clone)] pub fn use_auth_token_provider() {
pub struct StorageEntry<T> { let stored_token =
pub key: String, use_storage::<LocalStorage, _>(DIOXUS_FRONT_AUTH_TOKEN.to_owned(), AuthTokenState::default);
pub value: T,
use_context_provider(move || stored_token);
} }
pub trait PersistentWrite<T: Serialize + Clone> { pub fn use_auth_token() -> Signal<AuthTokenState> {
fn persistent_set(atom_ref: &UseAtomRef<Option<T>>, entry: Option<T>); use_context()
} }
impl PersistentWrite<AuthTokenState> for AuthTokenState { pub fn use_auth_request_provider() {
fn persistent_set( let stored_req = use_storage::<LocalStorage, _>(
atom_ref: &UseAtomRef<Option<AuthTokenState>>, DIOXUS_FRONT_AUTH_REQUEST.to_owned(),
entry: Option<AuthTokenState>, AuthRequestState::default,
) { );
*atom_ref.write() = entry.clone();
LocalStorage::set(DIOXUS_FRONT_AUTH_TOKEN, entry).unwrap(); use_context_provider(move || stored_req);
}
} }
impl PersistentWrite<AuthRequestState> for AuthRequestState { pub fn use_auth_request() -> Signal<AuthRequestState> {
fn persistent_set( use_context()
atom_ref: &UseAtomRef<Option<AuthRequestState>>, }
entry: Option<AuthRequestState>,
) { pub fn auth_request() -> Signal<AuthRequestState> {
*atom_ref.write() = entry.clone(); consume_context()
LocalStorage::set(DIOXUS_FRONT_AUTH_REQUEST, entry).unwrap();
}
} }
+105 -132
View File
@@ -1,3 +1,4 @@
use crate::storage::{auth_request, use_auth_request, use_auth_token};
use crate::{ use crate::{
oidc::{ oidc::{
authorize_url, email, exchange_refresh_token, init_oidc_client, log_out_url, authorize_url, email, exchange_refresh_token, init_oidc_client, log_out_url,
@@ -5,149 +6,124 @@ use crate::{
}, },
props::client::ClientProps, props::client::ClientProps,
router::Route, router::Route,
storage::PersistentWrite, CLIENT,
FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
}; };
use anyhow::Result;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus::router::prelude::{Link, Outlet}; use dioxus::router::prelude::{Link, Outlet};
use fermi::*;
use openidconnect::{url::Url, OAuth2TokenResponse, TokenResponse}; use openidconnect::{url::Url, OAuth2TokenResponse, TokenResponse};
#[component] #[component]
pub fn LogOut(cx: Scope<ClientProps>) -> Element { pub fn LogOut() -> Element {
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN); let mut auth_token = use_auth_token();
let fermi_auth_token_read = fermi_auth_token.read().clone(); let log_out_url_state = use_signal(|| None::<Option<Result<Url>>>);
let log_out_url_state = use_signal(|| None::<Option<Result<Url, crate::errors::Error>>>); match auth_token().id_token {
cx.render(match fermi_auth_token_read { Some(id_token) => match &*log_out_url_state.read() {
Some(fermi_auth_token_read) => match fermi_auth_token_read.id_token.clone() { Some(log_out_url_result) => match log_out_url_result {
Some(id_token) => match log_out_url_state.get() { Some(uri) => match uri {
Some(log_out_url_result) => match log_out_url_result { Ok(uri) => {
Some(uri) => match uri { rsx! {
Ok(uri) => { Link {
rsx! { onclick: move |_| {
Link { auth_token.take();
onclick: move |_| { },
{ to: uri.to_string(),
AuthTokenState::persistent_set( "Log out"
fermi_auth_token,
Some(AuthTokenState::default()),
);
}
},
to: uri.to_string(),
"Log out"
}
} }
} }
Err(error) => { }
rsx! { div { "Failed to load disconnection url: {error:?}" } } Err(error) => {
} rsx! { div { "Failed to load disconnection url: {error:?}" } }
},
None => {
rsx! { div { "Loading... Please wait" } }
} }
}, },
None => { None => {
let logout_url_task = move || { rsx! { div { "Loading... Please wait" } }
cx.spawn({
let log_out_url_state = log_out_url_state.to_owned();
async move {
let logout_url = log_out_url(id_token).await;
let logout_url_option = Some(logout_url);
log_out_url_state.set(Some(logout_url_option));
}
})
};
logout_url_task();
rsx! { div { "Loading log out url... Please wait" } }
} }
}, },
None => { None => {
rsx! {{}} let logout_url_task = move || {
spawn({
let mut log_out_url_state = log_out_url_state.to_owned();
async move {
let logout_url = log_out_url(id_token).await;
let logout_url_option = Some(logout_url);
log_out_url_state.set(Some(logout_url_option));
}
})
};
logout_url_task();
rsx! { div { "Loading log out url... Please wait" } }
} }
}, },
None => { None => {
rsx! {{}} rsx! {{}}
} }
}) }
} }
#[component] #[component]
pub fn RefreshToken(cx: Scope<ClientProps>) -> Element { pub fn RefreshToken(props: ClientProps) -> Element {
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN); let mut auth_token = use_auth_token();
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST); match auth_token().refresh_token {
let fermi_auth_token_read = fermi_auth_token.read().clone(); Some(refresh_token) => {
cx.render(match fermi_auth_token_read { rsx! { div {
Some(fermi_auth_client_read) => match fermi_auth_client_read.refresh_token { onmounted: {
Some(refresh_token) => { move |_| {
let fermi_auth_token = fermi_auth_token.to_owned(); let client = props.client.clone();
let fermi_auth_request = fermi_auth_request.to_owned(); let refresh_token = refresh_token.clone();
let client = cx.props.client.clone();
let exchange_refresh_token_spawn = move || {
cx.spawn({
async move { async move {
let exchange_refresh_token = let exchange_refresh_token =
exchange_refresh_token(client, refresh_token).await; exchange_refresh_token(client, refresh_token).await;
match exchange_refresh_token { match exchange_refresh_token {
Ok(response_token) => { Ok(response_token) => {
AuthTokenState::persistent_set( auth_token.set(AuthTokenState {
&fermi_auth_token, id_token: response_token.id_token().cloned(),
Some(AuthTokenState { refresh_token: response_token.refresh_token().cloned(),
id_token: response_token.id_token().cloned(), });
refresh_token: response_token.refresh_token().cloned(),
}),
);
} }
Err(_error) => { Err(_error) => {
AuthTokenState::persistent_set( auth_token.take();
&fermi_auth_token, auth_request().take();
Some(AuthTokenState::default()),
);
AuthRequestState::persistent_set(
&fermi_auth_request,
Some(AuthRequestState::default()),
);
} }
} }
} }
}) }
}; },
exchange_refresh_token_spawn(); "Refreshing session, please wait"
rsx! { div { "Refreshing session, please wait" } } } }
}
None => {
rsx! { div { "Id token expired and no refresh token found" } }
}
},
None => {
rsx! {{}}
} }
}) None => {
rsx! { div { "Id token expired and no refresh token found" } }
}
}
} }
#[component] #[component]
pub fn LoadClient() -> Element { pub fn LoadClient() -> Element {
let init_client_future = use_future(move || async move { init_oidc_client().await }); let init_client_future = use_resource(move || async move { init_oidc_client().await });
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(&FERMI_CLIENT); match &*init_client_future.read_unchecked() {
cx.render(match init_client_future.value() { Some(Ok((client_id, client))) => rsx! {
Some(client_props) => match client_props { div {
Ok((client_id, client)) => { onmounted: {
*fermi_client.write() = ClientState { let client_id = client_id.clone();
oidc_client: Some(ClientProps::new(client_id.clone(), client.clone())), let client = client.clone();
}; move |_| {
rsx! { *CLIENT.write() = ClientState {
div { "Client successfully loaded" } oidc_client: Some(ClientProps::new(client_id.clone(), client.clone())),
Outlet::<Route> {} };
} }
} },
Err(error) => { "Client successfully loaded"
log::info! {"Failed to load client: {:?}", error};
rsx! {
div { "Failed to load client: {error:?}" }
Outlet::<Route> {}
}
} }
Outlet::<Route> {}
}, },
Some(Err(error)) => {
log::info! {"Failed to load client: {:?}", error};
rsx! {
div { "Failed to load client: {error:?}" }
Outlet::<Route> {}
}
}
None => { None => {
rsx! { rsx! {
div { div {
@@ -156,34 +132,31 @@ pub fn LoadClient() -> Element {
} }
} }
} }
}) }
} }
#[component] #[component]
pub fn AuthHeader() -> Element { pub fn AuthHeader() -> Element {
let auth_token = use_atom_ref(&FERMI_AUTH_TOKEN); let client = CLIENT.read().oidc_client.clone();
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST); let mut auth_request = use_auth_request();
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(&FERMI_CLIENT); let auth_token = use_auth_token();
let client = fermi_client.read().oidc_client.clone(); match (client, auth_request(), auth_token()) {
let auth_request_read = fermi_auth_request.read().clone();
let auth_token_read = auth_token.read().clone();
cx.render(match (client, auth_request_read, auth_token_read) {
// We have everything we need to attempt to authenticate the user // We have everything we need to attempt to authenticate the user
(Some(client_props), Some(auth_request), Some(auth_token)) => { (Some(client_props), current_auth_request, current_auth_token) => {
match auth_request.auth_request { match current_auth_request.auth_request {
Some(auth_request) => { Some(new_auth_request) => {
match auth_token.id_token { match current_auth_token.id_token {
Some(id_token) => { Some(id_token) => {
match email( match email(
client_props.client.clone(), client_props.client.clone(),
id_token.clone(), id_token.clone(),
auth_request.nonce.clone(), new_auth_request.nonce.clone(),
) { ) {
Ok(email) => { Ok(email) => {
rsx! { rsx! {
div { div {
div { {email} } div { {email} }
LogOut { client_id: client_props.client_id, client: client_props.client } LogOut {}
Outlet::<Route> {} Outlet::<Route> {}
} }
} }
@@ -217,7 +190,7 @@ pub fn AuthHeader() -> Element {
None => { None => {
rsx! { rsx! {
div { div {
Link { to: auth_request.authorize_url.clone(), "Log in" } Link { to: new_auth_request.authorize_url.clone(), "Log in" }
Outlet::<Route> {} Outlet::<Route> {}
} }
} }
@@ -225,14 +198,18 @@ pub fn AuthHeader() -> Element {
} }
} }
None => { None => {
let auth_request = authorize_url(client_props.client); rsx! { div {
AuthRequestState::persistent_set( onmounted: {
fermi_auth_request, let client = client_props.client;
Some(AuthRequestState { move |_| {
auth_request: Some(auth_request), let new_auth_request = authorize_url(client.clone());
}), auth_request.set(AuthRequestState {
); auth_request: Some(new_auth_request),
rsx! { div { "Loading nonce" } } });
}
},
"Loading nonce"
} }
} }
} }
} }
@@ -240,9 +217,5 @@ pub fn AuthHeader() -> Element {
(None, _, _) => { (None, _, _) => {
rsx! { LoadClient {} } rsx! { LoadClient {} }
} }
// We need everything loaded before doing anything }
(_client, _auth_request, _auth_token) => {
rsx! {{}}
}
})
} }
+50 -50
View File
@@ -1,29 +1,28 @@
use crate::{ use crate::{
oidc::{token_response, AuthRequestState, AuthTokenState}, oidc::{token_response, AuthTokenState},
router::Route, router::Route,
storage::PersistentWrite, storage::{auth_request, use_auth_token},
DIOXUS_FRONT_URL, FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT, CLIENT,
}; };
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus::router::prelude::{Link, NavigationTarget}; use dioxus::router::prelude::Link;
use fermi::*;
use openidconnect::{OAuth2TokenResponse, TokenResponse}; use openidconnect::{OAuth2TokenResponse, TokenResponse};
#[component] #[component]
pub fn Login(query_string: String) -> Element { pub fn Login(query_string: String) -> Element {
let fermi_client = use_atom_ref(&FERMI_CLIENT); let client = CLIENT.read().oidc_client.clone();
let fermi_auth_token = use_atom_ref(&FERMI_AUTH_TOKEN); let mut auth_token = use_auth_token();
let home_url: NavigationTarget<Route> = DIOXUS_FRONT_URL.parse().unwrap(); let current_auth_token = auth_token();
let fermi_auth_request = use_atom_ref(&FERMI_AUTH_REQUEST); match client {
let client = fermi_client.read().oidc_client.clone(); Some(client_props) => {
let auth_token_read = fermi_auth_token.read().clone(); match (
cx.render(match (client, auth_token_read) { current_auth_token.id_token,
(Some(client_props), Some(auth_token_read)) => { current_auth_token.refresh_token,
match (auth_token_read.id_token, auth_token_read.refresh_token) { ) {
(Some(_id_token), Some(_refresh_token)) => { (Some(_id_token), Some(_refresh_token)) => {
rsx! { rsx! {
div { "Sign in successful" } div { "Sign in successful" }
Link { to: home_url, "Go back home" } Link { to: Route::Home {}, "Go back home" }
} }
} }
// If the refresh token is set but not the id_token, there was an error, we just go back home and reset their value // If the refresh token is set but not the id_token, there was an error, we just go back home and reset their value
@@ -31,13 +30,10 @@ pub fn Login(query_string: String) -> Element {
rsx! { rsx! {
div { "Error while attempting to log in" } div { "Error while attempting to log in" }
Link { Link {
to: home_url, to: Route::Home {},
onclick: move |_| { onclick: move |_| {
AuthTokenState::persistent_set(fermi_auth_token, Some(AuthTokenState::default())); auth_token.take();
AuthRequestState::persistent_set( auth_request().take();
fermi_auth_request,
Some(AuthRequestState::default()),
);
}, },
"Go back home" "Go back home"
} }
@@ -45,42 +41,46 @@ pub fn Login(query_string: String) -> Element {
} }
(None, None) => { (None, None) => {
let mut query_pairs = form_urlencoded::parse(query_string.as_bytes()); let mut query_pairs = form_urlencoded::parse(query_string.as_bytes());
let code_pair = query_pairs.find(|(key, _value)| key == "code"); let code_pair = query_pairs.find(|(key, _value)| key == "code");
match code_pair { match code_pair {
Some((_key, code)) => { Some((_key, code)) => {
let auth_code = code.to_string(); let code = code.to_string();
let token_response_spawn = move ||{ rsx! { div {
cx.spawn({ onmounted: {
let fermi_auth_token = fermi_auth_token.to_owned(); move |_| {
async move { let auth_code = code.to_string();
let token_response_result = token_response(client_props.client, auth_code).await; let client_props = client_props.clone();
match token_response_result{ async move {
Ok(token_response) => { let token_response_result =
let id_token = token_response.id_token().unwrap(); token_response(client_props.client, auth_code).await;
AuthTokenState::persistent_set(&fermi_auth_token, Some(AuthTokenState { match token_response_result {
id_token: Some(id_token.clone()), Ok(token_response) => {
refresh_token: token_response.refresh_token().cloned() let id_token = token_response.id_token().unwrap();
})); auth_token.set(AuthTokenState {
} id_token: Some(id_token.clone()),
Err(error) => { refresh_token: token_response
log::warn!{"{error}"}; .refresh_token()
.cloned(),
});
}
Err(error) => {
log::warn! {"{error}"};
}
}
} }
} }
} }
}) }}
}; }
token_response_spawn(); None => {
rsx!{ div {} } rsx! { div { "No code provided" } }
}
} }
None => {
rsx! { div { "No code provided" } }
}
}
} }
} }
} }
(_, _) => { _ => {
rsx! {{}} rsx! {{}}
} }
}) }
} }