header.rs 8.6 KB

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