header.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. use crate::{
  2. oidc::{
  3. authorize_url, email, exchange_refresh_token, init_oidc_client, log_out_url,
  4. AuthRequestState, AuthTokenState, ClientState,
  5. },
  6. props::client::ClientProps,
  7. router::Route,
  8. storage::PersistentWrite,
  9. FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
  10. };
  11. use dioxus::prelude::*;
  12. use dioxus_router::prelude::{Link, Outlet};
  13. use fermi::*;
  14. use openidconnect::{url::Url, OAuth2TokenResponse, TokenResponse};
  15. #[component]
  16. pub fn LogOut(cx: Scope<ClientProps>) -> Element {
  17. let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
  18. let fermi_auth_token_read = fermi_auth_token.read().clone();
  19. let log_out_url_state = use_state(cx, || None::<Option<Result<Url, crate::errors::Error>>>);
  20. cx.render(match fermi_auth_token_read {
  21. Some(fermi_auth_token_read) => match fermi_auth_token_read.id_token.clone() {
  22. Some(id_token) => match log_out_url_state.get() {
  23. Some(log_out_url_result) => match log_out_url_result {
  24. Some(uri) => match uri {
  25. Ok(uri) => {
  26. rsx! {
  27. Link {
  28. onclick: move |_| {
  29. {
  30. AuthTokenState::persistent_set(
  31. fermi_auth_token,
  32. Some(AuthTokenState::default()),
  33. );
  34. }
  35. },
  36. to: uri.to_string(),
  37. "Log out"
  38. }
  39. }
  40. }
  41. Err(error) => {
  42. rsx! {
  43. div { format!{"Failed to load disconnection url: {:?}", error} }
  44. }
  45. }
  46. },
  47. None => {
  48. rsx! { div { "Loading... Please wait" } }
  49. }
  50. },
  51. None => {
  52. let logout_url_task = move || {
  53. cx.spawn({
  54. let log_out_url_state = log_out_url_state.to_owned();
  55. async move {
  56. let logout_url = log_out_url(id_token).await;
  57. let logout_url_option = Some(logout_url);
  58. log_out_url_state.set(Some(logout_url_option));
  59. }
  60. })
  61. };
  62. logout_url_task();
  63. rsx! { div{"Loading log out url... Please wait"}}
  64. }
  65. },
  66. None => {
  67. rsx! {{}}
  68. }
  69. },
  70. None => {
  71. rsx! {{}}
  72. }
  73. })
  74. }
  75. #[component]
  76. pub fn RefreshToken(cx: Scope<ClientProps>) -> Element {
  77. let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
  78. let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
  79. let fermi_auth_token_read = fermi_auth_token.read().clone();
  80. cx.render(match fermi_auth_token_read {
  81. Some(fermi_auth_client_read) => match fermi_auth_client_read.refresh_token {
  82. Some(refresh_token) => {
  83. let fermi_auth_token = fermi_auth_token.to_owned();
  84. let fermi_auth_request = fermi_auth_request.to_owned();
  85. let client = cx.props.client.clone();
  86. let exchange_refresh_token_spawn = move || {
  87. cx.spawn({
  88. async move {
  89. let exchange_refresh_token =
  90. exchange_refresh_token(client, refresh_token).await;
  91. match exchange_refresh_token {
  92. Ok(response_token) => {
  93. AuthTokenState::persistent_set(
  94. &fermi_auth_token,
  95. Some(AuthTokenState {
  96. id_token: response_token.id_token().cloned(),
  97. refresh_token: response_token.refresh_token().cloned(),
  98. }),
  99. );
  100. }
  101. Err(_error) => {
  102. AuthTokenState::persistent_set(
  103. &fermi_auth_token,
  104. Some(AuthTokenState::default()),
  105. );
  106. AuthRequestState::persistent_set(
  107. &fermi_auth_request,
  108. Some(AuthRequestState::default()),
  109. );
  110. }
  111. }
  112. }
  113. })
  114. };
  115. exchange_refresh_token_spawn();
  116. rsx! { div { "Refreshing session, please wait" } }
  117. }
  118. None => {
  119. rsx! { div { "Id token expired and no refresh token found" } }
  120. }
  121. },
  122. None => {
  123. rsx! {{}}
  124. }
  125. })
  126. }
  127. #[component]
  128. pub fn LoadClient(cx: Scope) -> Element {
  129. let init_client_future = use_future(cx, (), |_| async move { init_oidc_client().await });
  130. let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
  131. cx.render(match init_client_future.value() {
  132. Some(client_props) => match client_props {
  133. Ok((client_id, client)) => {
  134. *fermi_client.write() = ClientState {
  135. oidc_client: Some(ClientProps::new(client_id.clone(), client.clone())),
  136. };
  137. rsx! {
  138. div { "Client successfully loaded" }
  139. Outlet::<Route> {}
  140. }
  141. }
  142. Err(error) => {
  143. rsx! {
  144. div { format!{"Failed to load client: {:?}", error} }
  145. log::info!{"Failed to load client: {:?}", error},
  146. Outlet::<Route> {}
  147. }
  148. }
  149. },
  150. None => {
  151. rsx! {
  152. div {
  153. div { "Loading client, please wait" }
  154. Outlet::<Route> {}
  155. }
  156. }
  157. }
  158. })
  159. }
  160. #[component]
  161. pub fn AuthHeader(cx: Scope) -> Element {
  162. let auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
  163. let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
  164. let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
  165. let client = fermi_client.read().oidc_client.clone();
  166. let auth_request_read = fermi_auth_request.read().clone();
  167. let auth_token_read = auth_token.read().clone();
  168. cx.render(match (client, auth_request_read, auth_token_read) {
  169. // We have everything we need to attempt to authenticate the user
  170. (Some(client_props), Some(auth_request), Some(auth_token)) => {
  171. match auth_request.auth_request {
  172. Some(auth_request) => {
  173. match auth_token.id_token {
  174. Some(id_token) => {
  175. match email(
  176. client_props.client.clone(),
  177. id_token.clone(),
  178. auth_request.nonce.clone(),
  179. ) {
  180. Ok(email) => {
  181. rsx! {
  182. div {
  183. div { email }
  184. LogOut { client_id: client_props.client_id, client: client_props.client }
  185. Outlet::<Route> {}
  186. }
  187. }
  188. }
  189. // Id token failed to be decoded
  190. Err(error) => match error {
  191. // Id token failed to be decoded because it expired, we refresh it
  192. openidconnect::ClaimsVerificationError::Expired(_message) => {
  193. log::info!("Token expired");
  194. rsx! {
  195. div {
  196. RefreshToken {client_id: client_props.client_id, client: client_props.client}
  197. Outlet::<Route> {}
  198. }
  199. }
  200. }
  201. // Other issue with token decoding
  202. _ => {
  203. log::info!("Other issue with token");
  204. rsx! {
  205. div {
  206. div { error.to_string() }
  207. Outlet::<Route> {}
  208. }
  209. }
  210. }
  211. },
  212. }
  213. }
  214. // User is not logged in
  215. None => {
  216. rsx! {
  217. div {
  218. Link { to: auth_request.authorize_url.clone(), "Log in" }
  219. Outlet::<Route> {}
  220. }
  221. }
  222. }
  223. }
  224. }
  225. None => {
  226. let auth_request = authorize_url(client_props.client);
  227. AuthRequestState::persistent_set(
  228. fermi_auth_request,
  229. Some(AuthRequestState {
  230. auth_request: Some(auth_request),
  231. }),
  232. );
  233. rsx! { div { "Loading nonce" } }
  234. }
  235. }
  236. }
  237. // Client is not initialized yet, we need it for everything
  238. (None, _, _) => {
  239. rsx! { LoadClient {} }
  240. }
  241. // We need everything loaded before doing anything
  242. (_client, _auth_request, _auth_token) => {
  243. rsx! {{}}
  244. }
  245. })
  246. }