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:
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"
|
||||||
Generated
+6372
File diff suppressed because it is too large.
Load diff
@@ -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" }
|
||||||
@@ -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>,
|
|
||||||
>,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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! {{}}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
@@ -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! {{}}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user