mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	Merge branch 'main' into feature/dark-theme
This commit is contained in:
		
						commit
						0bce04e2a7
					
				@ -92,10 +92,8 @@ impl Component for App {
 | 
				
			|||||||
            redirect_to: Self::get_redirect_route(ctx),
 | 
					            redirect_to: Self::get_redirect_route(ctx),
 | 
				
			||||||
            password_reset_enabled: None,
 | 
					            password_reset_enabled: None,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let link = ctx.link().clone();
 | 
					        ctx.link().send_future(async move {
 | 
				
			||||||
        wasm_bindgen_futures::spawn_local(async move {
 | 
					            Msg::PasswordResetProbeFinished(HostService::probe_password_reset().await)
 | 
				
			||||||
            let result = HostService::probe_password_reset().await;
 | 
					 | 
				
			||||||
            link.send_message(Msg::PasswordResetProbeFinished(result));
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        app.apply_initial_redirections(ctx);
 | 
					        app.apply_initial_redirections(ctx);
 | 
				
			||||||
        app
 | 
					        app
 | 
				
			||||||
@ -157,34 +155,33 @@ impl Component for App {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl App {
 | 
					impl App {
 | 
				
			||||||
 | 
					    // Get the page to land on after logging in, defaulting to the index.
 | 
				
			||||||
    fn get_redirect_route(ctx: &Context<Self>) -> Option<AppRoute> {
 | 
					    fn get_redirect_route(ctx: &Context<Self>) -> Option<AppRoute> {
 | 
				
			||||||
        let history = ctx.link().history().unwrap();
 | 
					        let route = ctx.link().history().unwrap().location().route::<AppRoute>();
 | 
				
			||||||
        let route = history.location().route::<AppRoute>();
 | 
					        route.filter(|route| {
 | 
				
			||||||
        route.and_then(|route| match route {
 | 
					            !matches!(
 | 
				
			||||||
 | 
					                route,
 | 
				
			||||||
                AppRoute::Index
 | 
					                AppRoute::Index
 | 
				
			||||||
                    | AppRoute::Login
 | 
					                    | AppRoute::Login
 | 
				
			||||||
                    | AppRoute::StartResetPassword
 | 
					                    | AppRoute::StartResetPassword
 | 
				
			||||||
            | AppRoute::FinishResetPassword { token: _ } => None,
 | 
					                    | AppRoute::FinishResetPassword { token: _ }
 | 
				
			||||||
            _ => Some(route),
 | 
					            )
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn apply_initial_redirections(&self, ctx: &Context<Self>) {
 | 
					    fn apply_initial_redirections(&self, ctx: &Context<Self>) {
 | 
				
			||||||
        let history = ctx.link().history().unwrap();
 | 
					        let history = ctx.link().history().unwrap();
 | 
				
			||||||
        let route = history.location().route::<AppRoute>();
 | 
					        let route = history.location().route::<AppRoute>();
 | 
				
			||||||
        let redirection = if let Some(route) = route {
 | 
					        let redirection = match (route, &self.user_info, &self.redirect_to) {
 | 
				
			||||||
            if matches!(
 | 
					            (
 | 
				
			||||||
                route,
 | 
					                Some(AppRoute::StartResetPassword | AppRoute::FinishResetPassword { token: _ }),
 | 
				
			||||||
                AppRoute::StartResetPassword | AppRoute::FinishResetPassword { token: _ }
 | 
					                _,
 | 
				
			||||||
            ) && self.password_reset_enabled == Some(false)
 | 
					                _,
 | 
				
			||||||
            {
 | 
					            ) if self.password_reset_enabled == Some(false) => Some(AppRoute::Login),
 | 
				
			||||||
                Some(AppRoute::Login)
 | 
					            (None, _, _) | (_, None, _) => Some(AppRoute::Login),
 | 
				
			||||||
            } else {
 | 
					            // User is logged in, a URL was given, don't redirect.
 | 
				
			||||||
                match &self.user_info {
 | 
					            (_, Some(_), Some(_)) => None,
 | 
				
			||||||
                    None => Some(AppRoute::Login),
 | 
					            (_, Some((user_name, is_admin)), None) => {
 | 
				
			||||||
                    Some((user_name, is_admin)) => match &self.redirect_to {
 | 
					 | 
				
			||||||
                        Some(url) => Some(url.clone()),
 | 
					 | 
				
			||||||
                        None => {
 | 
					 | 
				
			||||||
                if *is_admin {
 | 
					                if *is_admin {
 | 
				
			||||||
                    Some(AppRoute::ListUsers)
 | 
					                    Some(AppRoute::ListUsers)
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
@ -193,11 +190,6 @@ impl App {
 | 
				
			|||||||
                    })
 | 
					                    })
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            Some(AppRoute::Login)
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        if let Some(redirect_to) = redirection {
 | 
					        if let Some(redirect_to) = redirection {
 | 
				
			||||||
            history.push(redirect_to);
 | 
					            history.push(redirect_to);
 | 
				
			||||||
@ -340,6 +332,7 @@ impl App {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  } else { html!{} }
 | 
					                  } else { html!{} }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                { self.view_user_menu(ctx) } // TODO migrate chagnes from above
 | 
				
			||||||
                <DarkModeToggle />
 | 
					                <DarkModeToggle />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@ -347,6 +340,52 @@ impl App {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn view_user_menu(&self, ctx: &Context<Self>) -> Html {
 | 
				
			||||||
 | 
					        if let Some((user_id, _)) = &self.user_info {
 | 
				
			||||||
 | 
					            let link = ctx.link();
 | 
				
			||||||
 | 
					            html! {
 | 
				
			||||||
 | 
					              <div class="dropdown text-end">
 | 
				
			||||||
 | 
					                <a href="#"
 | 
				
			||||||
 | 
					                  class="d-block link-dark text-decoration-none dropdown-toggle"
 | 
				
			||||||
 | 
					                  id="dropdownUser"
 | 
				
			||||||
 | 
					                  data-bs-toggle="dropdown"
 | 
				
			||||||
 | 
					                  aria-expanded="false">
 | 
				
			||||||
 | 
					                  <svg xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					                    width="32"
 | 
				
			||||||
 | 
					                    height="32"
 | 
				
			||||||
 | 
					                    fill="currentColor"
 | 
				
			||||||
 | 
					                    class="bi bi-person-circle"
 | 
				
			||||||
 | 
					                    viewBox="0 0 16 16">
 | 
				
			||||||
 | 
					                    <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
 | 
				
			||||||
 | 
					                    <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
 | 
				
			||||||
 | 
					                  </svg>
 | 
				
			||||||
 | 
					                  <span class="ms-2">
 | 
				
			||||||
 | 
					                    {user_id}
 | 
				
			||||||
 | 
					                  </span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                <ul
 | 
				
			||||||
 | 
					                  class="dropdown-menu text-small dropdown-menu-lg-end"
 | 
				
			||||||
 | 
					                  aria-labelledby="dropdownUser1"
 | 
				
			||||||
 | 
					                  style="">
 | 
				
			||||||
 | 
					                  <li>
 | 
				
			||||||
 | 
					                    <Link
 | 
				
			||||||
 | 
					                      classes="dropdown-item"
 | 
				
			||||||
 | 
					                      to={AppRoute::UserDetails{ user_id: user_id.clone() }}>
 | 
				
			||||||
 | 
					                      {"View details"}
 | 
				
			||||||
 | 
					                    </Link>
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                  <li><hr class="dropdown-divider" /></li>
 | 
				
			||||||
 | 
					                  <li>
 | 
				
			||||||
 | 
					                    <LogoutButton on_logged_out={link.callback(|_| Msg::Logout)} />
 | 
				
			||||||
 | 
					                  </li>
 | 
				
			||||||
 | 
					                </ul>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            html! {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn view_footer(&self) -> Html {
 | 
					    fn view_footer(&self) -> Html {
 | 
				
			||||||
        html! {
 | 
					        html! {
 | 
				
			||||||
          <footer class="text-center fixed-bottom bg-light py-2">
 | 
					          <footer class="text-center fixed-bottom bg-light py-2">
 | 
				
			||||||
 | 
				
			|||||||
@ -64,31 +64,17 @@ impl Component for Select {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct SelectOption;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(yew::Properties, Clone, PartialEq, Eq, Debug)]
 | 
					#[derive(yew::Properties, Clone, PartialEq, Eq, Debug)]
 | 
				
			||||||
pub struct SelectOptionProps {
 | 
					pub struct SelectOptionProps {
 | 
				
			||||||
    pub value: String,
 | 
					    pub value: String,
 | 
				
			||||||
    pub text: String,
 | 
					    pub text: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Component for SelectOption {
 | 
					#[function_component(SelectOption)]
 | 
				
			||||||
    type Message = ();
 | 
					pub fn select_option(props: &SelectOptionProps) -> Html {
 | 
				
			||||||
    type Properties = SelectOptionProps;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn create(_: &Context<Self>) -> Self {
 | 
					 | 
				
			||||||
        Self
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn update(&mut self, _: &Context<Self>, _: Self::Message) -> bool {
 | 
					 | 
				
			||||||
        false
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn view(&self, ctx: &Context<Self>) -> Html {
 | 
					 | 
				
			||||||
    html! {
 | 
					    html! {
 | 
				
			||||||
          <option value={ctx.props().value.clone()}>
 | 
					      <option value={props.value.clone()}>
 | 
				
			||||||
            {&ctx.props().text}
 | 
					        {&props.text}
 | 
				
			||||||
      </option>
 | 
					      </option>
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -65,6 +65,15 @@ async fn call_server_empty_response_with_error_message<Body: Serialize>(
 | 
				
			|||||||
    call_server(url, request, error_message).await.map(|_| ())
 | 
					    call_server(url, request, error_message).await.map(|_| ())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn set_cookies_from_jwt(response: login::ServerLoginResponse) -> Result<(String, bool)> {
 | 
				
			||||||
 | 
					    let jwt_claims = get_claims_from_jwt(response.token.as_str()).context("Could not parse JWT")?;
 | 
				
			||||||
 | 
					    let is_admin = jwt_claims.groups.contains("lldap_admin");
 | 
				
			||||||
 | 
					    set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp)
 | 
				
			||||||
 | 
					        .map(|_| set_cookie("is_admin", &is_admin.to_string(), &jwt_claims.exp))
 | 
				
			||||||
 | 
					        .map(|_| (jwt_claims.user.clone(), is_admin))
 | 
				
			||||||
 | 
					        .context("Error setting cookie")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl HostService {
 | 
					impl HostService {
 | 
				
			||||||
    pub async fn graphql_query<QueryType>(
 | 
					    pub async fn graphql_query<QueryType>(
 | 
				
			||||||
        variables: QueryType::Variables,
 | 
					        variables: QueryType::Variables,
 | 
				
			||||||
@ -87,9 +96,12 @@ impl HostService {
 | 
				
			|||||||
            })
 | 
					            })
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let request_body = QueryType::build_query(variables);
 | 
					        let request_body = QueryType::build_query(variables);
 | 
				
			||||||
        let response = call_server("/api/graphql", Some(request_body), error_message).await?;
 | 
					        call_server_json_with_error_message::<graphql_client::Response<_>, _>(
 | 
				
			||||||
        serde_json::from_str(&response)
 | 
					            "/api/graphql",
 | 
				
			||||||
            .context("Could not parse response")
 | 
					            Some(request_body),
 | 
				
			||||||
 | 
					            error_message,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
        .and_then(unwrap_graphql_response)
 | 
					        .and_then(unwrap_graphql_response)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -105,26 +117,13 @@ impl HostService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn login_finish(request: login::ClientLoginFinishRequest) -> Result<(String, bool)> {
 | 
					    pub async fn login_finish(request: login::ClientLoginFinishRequest) -> Result<(String, bool)> {
 | 
				
			||||||
        let set_cookies = |jwt_claims: JWTClaims| {
 | 
					        call_server_json_with_error_message::<login::ServerLoginResponse, _>(
 | 
				
			||||||
            let is_admin = jwt_claims.groups.contains("lldap_admin");
 | 
					 | 
				
			||||||
            set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp)
 | 
					 | 
				
			||||||
                .map(|_| set_cookie("is_admin", &is_admin.to_string(), &jwt_claims.exp))
 | 
					 | 
				
			||||||
                .map(|_| (jwt_claims.user.clone(), is_admin))
 | 
					 | 
				
			||||||
                .context("Error clearing cookie")
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        let response = call_server(
 | 
					 | 
				
			||||||
            "/auth/opaque/login/finish",
 | 
					            "/auth/opaque/login/finish",
 | 
				
			||||||
            Some(request),
 | 
					            Some(request),
 | 
				
			||||||
            "Could not finish authentication",
 | 
					            "Could not finish authentication",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .await?;
 | 
					        .await
 | 
				
			||||||
        serde_json::from_str::<login::ServerLoginResponse>(&response)
 | 
					        .and_then(set_cookies_from_jwt)
 | 
				
			||||||
            .context("Could not parse response")
 | 
					 | 
				
			||||||
            .and_then(|r| {
 | 
					 | 
				
			||||||
                get_claims_from_jwt(r.token.as_str())
 | 
					 | 
				
			||||||
                    .context("Could not parse response")
 | 
					 | 
				
			||||||
                    .and_then(set_cookies)
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn register_start(
 | 
					    pub async fn register_start(
 | 
				
			||||||
@ -150,22 +149,13 @@ impl HostService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn refresh() -> Result<(String, bool)> {
 | 
					    pub async fn refresh() -> Result<(String, bool)> {
 | 
				
			||||||
        let set_cookies = |jwt_claims: JWTClaims| {
 | 
					        call_server_json_with_error_message::<login::ServerLoginResponse, _>(
 | 
				
			||||||
            let is_admin = jwt_claims.groups.contains("lldap_admin");
 | 
					            "/auth/refresh",
 | 
				
			||||||
            set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp)
 | 
					            NO_BODY,
 | 
				
			||||||
                .map(|_| set_cookie("is_admin", &is_admin.to_string(), &jwt_claims.exp))
 | 
					            "Could not start authentication: ",
 | 
				
			||||||
                .map(|_| (jwt_claims.user.clone(), is_admin))
 | 
					        )
 | 
				
			||||||
                .context("Error clearing cookie")
 | 
					        .await
 | 
				
			||||||
        };
 | 
					        .and_then(set_cookies_from_jwt)
 | 
				
			||||||
        let response =
 | 
					 | 
				
			||||||
            call_server("/auth/refresh", NO_BODY, "Could not start authentication: ").await?;
 | 
					 | 
				
			||||||
        serde_json::from_str::<login::ServerLoginResponse>(&response)
 | 
					 | 
				
			||||||
            .context("Could not parse response")
 | 
					 | 
				
			||||||
            .and_then(|r| {
 | 
					 | 
				
			||||||
                get_claims_from_jwt(r.token.as_str())
 | 
					 | 
				
			||||||
                    .context("Could not parse response")
 | 
					 | 
				
			||||||
                    .and_then(set_cookies)
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // The `_request` parameter is to make it the same shape as the other functions.
 | 
					    // The `_request` parameter is to make it the same shape as the other functions.
 | 
				
			||||||
 | 
				
			|||||||
@ -102,7 +102,7 @@ impl<C: CommonComponent<C>> CommonComponentParts<C> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Call `method` from the backend with the given `request`, and pass the `callback` for the
 | 
					    /// Call `method` from the backend with the given `request`, and pass the `callback` for the
 | 
				
			||||||
    /// result. Returns whether _starting the call_ failed.
 | 
					    /// result.
 | 
				
			||||||
    pub fn call_backend<Fut, Cb, Resp>(&mut self, ctx: &Context<C>, fut: Fut, callback: Cb)
 | 
					    pub fn call_backend<Fut, Cb, Resp>(&mut self, ctx: &Context<C>, fut: Fut, callback: Cb)
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        Fut: Future<Output = Resp> + 'static,
 | 
					        Fut: Future<Output = Resp> + 'static,
 | 
				
			||||||
@ -134,29 +134,10 @@ impl<C: CommonComponent<C>> CommonComponentParts<C> {
 | 
				
			|||||||
        QueryType: GraphQLQuery + 'static,
 | 
					        QueryType: GraphQLQuery + 'static,
 | 
				
			||||||
        EnumCallback: Fn(Result<QueryType::ResponseData>) -> <C as Component>::Message + 'static,
 | 
					        EnumCallback: Fn(Result<QueryType::ResponseData>) -> <C as Component>::Message + 'static,
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        {
 | 
					        self.call_backend(
 | 
				
			||||||
            let mut running = self.is_task_running.lock().unwrap();
 | 
					            ctx,
 | 
				
			||||||
            assert!(!*running);
 | 
					            HostService::graphql_query::<QueryType>(variables, error_message),
 | 
				
			||||||
            *running = true;
 | 
					            enum_callback,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        let is_task_running = self.is_task_running.clone();
 | 
					 | 
				
			||||||
        ctx.link().send_future(async move {
 | 
					 | 
				
			||||||
            let res = HostService::graphql_query::<QueryType>(variables, error_message).await;
 | 
					 | 
				
			||||||
            *is_task_running.lock().unwrap() = false;
 | 
					 | 
				
			||||||
            enum_callback(res)
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /*
 | 
					 | 
				
			||||||
    pub(crate) fn read_file<Cb>(&mut self, file: web_sys::File, callback: Cb) -> Result<()>
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        Cb: FnOnce(FileData) -> <C as Component>::Message + 'static,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        self.task = AnyTask::ReaderTask(ReaderService::read_file(
 | 
					 | 
				
			||||||
            file,
 | 
					 | 
				
			||||||
            self.link.callback_once(callback),
 | 
					 | 
				
			||||||
        )?);
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user