mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	app: Migrate login to yew_form
This commit is contained in:
		
							parent
							
								
									1174bd73c7
								
							
						
					
					
						commit
						e37b6e44e7
					
				@ -1,114 +1,100 @@
 | 
				
			|||||||
use crate::infra::api::HostService;
 | 
					use crate::infra::api::HostService;
 | 
				
			||||||
use anyhow::{anyhow, Context, Result};
 | 
					use anyhow::{anyhow, bail, Context, Result};
 | 
				
			||||||
use lldap_auth::*;
 | 
					use lldap_auth::*;
 | 
				
			||||||
use wasm_bindgen::JsCast;
 | 
					use validator_derive::Validate;
 | 
				
			||||||
use yew::prelude::*;
 | 
					use yew::prelude::*;
 | 
				
			||||||
use yew::services::{fetch::FetchTask, ConsoleService};
 | 
					use yew::services::{fetch::FetchTask, ConsoleService};
 | 
				
			||||||
 | 
					use yew_form::Form;
 | 
				
			||||||
 | 
					use yew_form_derive::Model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct LoginForm {
 | 
					pub struct LoginForm {
 | 
				
			||||||
    link: ComponentLink<Self>,
 | 
					    link: ComponentLink<Self>,
 | 
				
			||||||
    on_logged_in: Callback<(String, bool)>,
 | 
					    on_logged_in: Callback<(String, bool)>,
 | 
				
			||||||
    error: Option<anyhow::Error>,
 | 
					    error: Option<anyhow::Error>,
 | 
				
			||||||
    node_ref: NodeRef,
 | 
					    form: Form<FormModel>,
 | 
				
			||||||
    login_start: Option<opaque::client::login::ClientLogin>,
 | 
					 | 
				
			||||||
    // Used to keep the request alive long enough.
 | 
					    // Used to keep the request alive long enough.
 | 
				
			||||||
    _task: Option<FetchTask>,
 | 
					    _task: Option<FetchTask>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The fields of the form, with the constraints.
 | 
				
			||||||
 | 
					#[derive(Model, Validate, PartialEq, Clone, Default)]
 | 
				
			||||||
 | 
					pub struct FormModel {
 | 
				
			||||||
 | 
					    #[validate(length(min = 1, message = "Missing username"))]
 | 
				
			||||||
 | 
					    username: String,
 | 
				
			||||||
 | 
					    #[validate(length(min = 8, message = "Invalid password. Min length: 8"))]
 | 
				
			||||||
 | 
					    password: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, PartialEq, Properties)]
 | 
					#[derive(Clone, PartialEq, Properties)]
 | 
				
			||||||
pub struct Props {
 | 
					pub struct Props {
 | 
				
			||||||
    pub on_logged_in: Callback<(String, bool)>,
 | 
					    pub on_logged_in: Callback<(String, bool)>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub enum Msg {
 | 
					pub enum Msg {
 | 
				
			||||||
 | 
					    Update,
 | 
				
			||||||
    Submit,
 | 
					    Submit,
 | 
				
			||||||
    AuthenticationStartResponse(Result<Box<login::ServerLoginStartResponse>>),
 | 
					    AuthenticationStartResponse(
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					            opaque::client::login::ClientLogin,
 | 
				
			||||||
 | 
					            Result<Box<login::ServerLoginStartResponse>>,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    AuthenticationFinishResponse(Result<(String, bool)>),
 | 
					    AuthenticationFinishResponse(Result<(String, bool)>),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn get_form_field(field_id: &str) -> Option<String> {
 | 
					 | 
				
			||||||
    let document = web_sys::window()?.document()?;
 | 
					 | 
				
			||||||
    Some(
 | 
					 | 
				
			||||||
        document
 | 
					 | 
				
			||||||
            .get_element_by_id(field_id)?
 | 
					 | 
				
			||||||
            .dyn_into::<web_sys::HtmlInputElement>()
 | 
					 | 
				
			||||||
            .ok()?
 | 
					 | 
				
			||||||
            .value(),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl LoginForm {
 | 
					impl LoginForm {
 | 
				
			||||||
    fn set_error(&mut self, error: anyhow::Error) {
 | 
					    fn handle_message(&mut self, msg: <Self as Component>::Message) -> Result<bool> {
 | 
				
			||||||
        ConsoleService::error(&error.to_string());
 | 
					 | 
				
			||||||
        self.error = Some(error);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn call_backend<M, Req, C, Resp>(&mut self, method: M, req: Req, callback: C) -> Result<()>
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        M: Fn(Req, Callback<Resp>) -> Result<FetchTask>,
 | 
					 | 
				
			||||||
        C: Fn(Resp) -> <Self as Component>::Message + 'static,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        self._task = Some(method(req, self.link.callback(callback))?);
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn handle_message(&mut self, msg: <Self as Component>::Message) -> Result<()> {
 | 
					 | 
				
			||||||
        match msg {
 | 
					        match msg {
 | 
				
			||||||
 | 
					            Msg::Update => Ok(true),
 | 
				
			||||||
            Msg::Submit => {
 | 
					            Msg::Submit => {
 | 
				
			||||||
                let username = get_form_field("username")
 | 
					                if !self.form.validate() {
 | 
				
			||||||
                    .ok_or_else(|| anyhow!("Could not get username from form"))?;
 | 
					                    bail!("Invalid inputs");
 | 
				
			||||||
                let password = get_form_field("password")
 | 
					                }
 | 
				
			||||||
                    .ok_or_else(|| anyhow!("Could not get password from form"))?;
 | 
					                let FormModel { username, password } = self.form.model();
 | 
				
			||||||
                let mut rng = rand::rngs::OsRng;
 | 
					                let mut rng = rand::rngs::OsRng;
 | 
				
			||||||
                let login_start_request = opaque::client::login::start_login(&password, &mut rng)
 | 
					                let opaque::client::login::ClientLoginStartResult { state, message } =
 | 
				
			||||||
                    .context("Could not initialize login")?;
 | 
					                    opaque::client::login::start_login(&password, &mut rng)
 | 
				
			||||||
                self.login_start = Some(login_start_request.state);
 | 
					                        .context("Could not initialize login")?;
 | 
				
			||||||
                let req = login::ClientLoginStartRequest {
 | 
					                let req = login::ClientLoginStartRequest {
 | 
				
			||||||
                    username,
 | 
					                    username,
 | 
				
			||||||
                    login_start_request: login_start_request.message,
 | 
					                    login_start_request: message,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                self.call_backend(
 | 
					                self._task = Some(HostService::login_start(
 | 
				
			||||||
                    HostService::login_start,
 | 
					 | 
				
			||||||
                    req,
 | 
					                    req,
 | 
				
			||||||
                    Msg::AuthenticationStartResponse,
 | 
					                    self.link
 | 
				
			||||||
                )?;
 | 
					                        .callback_once(move |r| Msg::AuthenticationStartResponse((state, r))),
 | 
				
			||||||
                Ok(())
 | 
					                )?);
 | 
				
			||||||
 | 
					                Ok(false)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Msg::AuthenticationStartResponse(Ok(res)) => {
 | 
					            Msg::AuthenticationStartResponse((login_start, res)) => {
 | 
				
			||||||
                debug_assert!(self.login_start.is_some());
 | 
					                let res = res.context("Could not log in (invalid response to login start)")?;
 | 
				
			||||||
                let login_finish = match opaque::client::login::finish_login(
 | 
					                let login_finish =
 | 
				
			||||||
                    self.login_start.as_ref().unwrap().clone(),
 | 
					                    match opaque::client::login::finish_login(login_start, res.credential_response)
 | 
				
			||||||
                    res.credential_response,
 | 
					                    {
 | 
				
			||||||
                ) {
 | 
					                        Err(e) => {
 | 
				
			||||||
                    Err(e) => {
 | 
					                            // Common error, we want to print a full error to the console but only a
 | 
				
			||||||
                        // Common error, we want to print a full error to the console but only a
 | 
					                            // simple one to the user.
 | 
				
			||||||
                        // simple one to the user.
 | 
					                            ConsoleService::error(&format!("Invalid username or password: {}", e));
 | 
				
			||||||
                        ConsoleService::error(&format!("Invalid username or password: {}", e));
 | 
					                            self.error = Some(anyhow!("Invalid username or password"));
 | 
				
			||||||
                        self.error = Some(anyhow!("Invalid username or password"));
 | 
					                            return Ok(true);
 | 
				
			||||||
                        return Ok(());
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                        Ok(l) => l,
 | 
				
			||||||
                    Ok(l) => l,
 | 
					                    };
 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                let req = login::ClientLoginFinishRequest {
 | 
					                let req = login::ClientLoginFinishRequest {
 | 
				
			||||||
                    server_data: res.server_data,
 | 
					                    server_data: res.server_data,
 | 
				
			||||||
                    credential_finalization: login_finish.message,
 | 
					                    credential_finalization: login_finish.message,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                self.call_backend(
 | 
					                self._task = Some(HostService::login_finish(
 | 
				
			||||||
                    HostService::login_finish,
 | 
					 | 
				
			||||||
                    req,
 | 
					                    req,
 | 
				
			||||||
                    Msg::AuthenticationFinishResponse,
 | 
					                    self.link.callback_once(Msg::AuthenticationFinishResponse),
 | 
				
			||||||
                )?;
 | 
					                )?);
 | 
				
			||||||
                Ok(())
 | 
					                Ok(false)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Msg::AuthenticationStartResponse(Err(e)) => Err(anyhow!(
 | 
					            Msg::AuthenticationFinishResponse(user_info) => {
 | 
				
			||||||
                "Could not log in (invalid response to login start): {}",
 | 
					                self.on_logged_in
 | 
				
			||||||
                e
 | 
					                    .emit(user_info.context("Could not log in")?);
 | 
				
			||||||
            )),
 | 
					                Ok(true)
 | 
				
			||||||
            Msg::AuthenticationFinishResponse(Ok(user_info)) => {
 | 
					 | 
				
			||||||
                self.on_logged_in.emit(user_info);
 | 
					 | 
				
			||||||
                Ok(())
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Msg::AuthenticationFinishResponse(Err(e)) => Err(anyhow!("Could not log in: {}", e)),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -122,18 +108,21 @@ impl Component for LoginForm {
 | 
				
			|||||||
            link,
 | 
					            link,
 | 
				
			||||||
            on_logged_in: props.on_logged_in,
 | 
					            on_logged_in: props.on_logged_in,
 | 
				
			||||||
            error: None,
 | 
					            error: None,
 | 
				
			||||||
            node_ref: NodeRef::default(),
 | 
					            form: Form::<FormModel>::new(FormModel::default()),
 | 
				
			||||||
            login_start: None,
 | 
					 | 
				
			||||||
            _task: None,
 | 
					            _task: None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn update(&mut self, msg: Self::Message) -> ShouldRender {
 | 
					    fn update(&mut self, msg: Self::Message) -> ShouldRender {
 | 
				
			||||||
        self.error = None;
 | 
					        self.error = None;
 | 
				
			||||||
        if let Err(e) = self.handle_message(msg) {
 | 
					        match self.handle_message(msg) {
 | 
				
			||||||
            self.set_error(e);
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                ConsoleService::error(&e.to_string());
 | 
				
			||||||
 | 
					                self.error = Some(e);
 | 
				
			||||||
 | 
					                true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Ok(b) => b,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        true
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn change(&mut self, _: Self::Properties) -> ShouldRender {
 | 
					    fn change(&mut self, _: Self::Properties) -> ShouldRender {
 | 
				
			||||||
@ -141,23 +130,25 @@ impl Component for LoginForm {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn view(&self) -> Html {
 | 
					    fn view(&self) -> Html {
 | 
				
			||||||
 | 
					        type Field = yew_form::Field<FormModel>;
 | 
				
			||||||
        html! {
 | 
					        html! {
 | 
				
			||||||
            <form
 | 
					            <form
 | 
				
			||||||
              ref=self.node_ref.clone()
 | 
					              class="form center-block col-sm-4 col-offset-4">
 | 
				
			||||||
              class="form center-block col-sm-4 col-offset-4"
 | 
					 | 
				
			||||||
              onsubmit=self.link.callback(|e: FocusEvent| { e.prevent_default(); Msg::Submit })>
 | 
					 | 
				
			||||||
                <div class="input-group">
 | 
					                <div class="input-group">
 | 
				
			||||||
                  <div class="input-group-prepend">
 | 
					                  <div class="input-group-prepend">
 | 
				
			||||||
                    <span class="input-group-text">
 | 
					                    <span class="input-group-text">
 | 
				
			||||||
                      <i class="bi-person-fill"/>
 | 
					                      <i class="bi-person-fill"/>
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <input
 | 
					                  <Field
 | 
				
			||||||
                    type="text"
 | 
					 | 
				
			||||||
                    class="form-control"
 | 
					                    class="form-control"
 | 
				
			||||||
                    id="username"
 | 
					                    class_invalid="is-invalid has-error"
 | 
				
			||||||
 | 
					                    class_valid="has-success"
 | 
				
			||||||
 | 
					                    form=&self.form
 | 
				
			||||||
 | 
					                    field_name="username"
 | 
				
			||||||
                    placeholder="Username"
 | 
					                    placeholder="Username"
 | 
				
			||||||
                    required=true />
 | 
					                    autocomplete="username"
 | 
				
			||||||
 | 
					                    oninput=self.link.callback(|_| Msg::Update) />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="input-group">
 | 
					                <div class="input-group">
 | 
				
			||||||
                  <div class="input-group-prepend">
 | 
					                  <div class="input-group-prepend">
 | 
				
			||||||
@ -165,18 +156,21 @@ impl Component for LoginForm {
 | 
				
			|||||||
                      <i class="bi-lock-fill"/>
 | 
					                      <i class="bi-lock-fill"/>
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <input
 | 
					                  <Field
 | 
				
			||||||
                    type="password"
 | 
					 | 
				
			||||||
                    class="form-control"
 | 
					                    class="form-control"
 | 
				
			||||||
                    id="password"
 | 
					                    class_invalid="is-invalid has-error"
 | 
				
			||||||
                    required=true
 | 
					                    class_valid="has-success"
 | 
				
			||||||
 | 
					                    form=&self.form
 | 
				
			||||||
 | 
					                    field_name="password"
 | 
				
			||||||
 | 
					                    input_type="password"
 | 
				
			||||||
                    placeholder="Email"
 | 
					                    placeholder="Email"
 | 
				
			||||||
                    autocomplete="current-password" />
 | 
					                    autocomplete="current-password" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="form-group">
 | 
					                <div class="form-group">
 | 
				
			||||||
                  <button
 | 
					                  <button
 | 
				
			||||||
                    type="submit"
 | 
					                    type="submit"
 | 
				
			||||||
                    class="btn btn-primary">
 | 
					                    class="btn btn-primary"
 | 
				
			||||||
 | 
					                    onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::Submit})>
 | 
				
			||||||
                    {"Login"}
 | 
					                    {"Login"}
 | 
				
			||||||
                  </button>
 | 
					                  </button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -93,7 +93,7 @@ impl UserDetails {
 | 
				
			|||||||
    fn view_messages(&self, error: &Option<Error>) -> Html {
 | 
					    fn view_messages(&self, error: &Option<Error>) -> Html {
 | 
				
			||||||
        if let Some(e) = error {
 | 
					        if let Some(e) = error {
 | 
				
			||||||
            html! {
 | 
					            html! {
 | 
				
			||||||
              <div>
 | 
					              <div class="alert alert-danger">
 | 
				
			||||||
                <span>{"Error: "}{e.to_string()}</span>
 | 
					                <span>{"Error: "}{e.to_string()}</span>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -205,7 +205,6 @@ impl Component for UserDetails {
 | 
				
			|||||||
                      <UserDetailsForm
 | 
					                      <UserDetailsForm
 | 
				
			||||||
                        user=u.clone()
 | 
					                        user=u.clone()
 | 
				
			||||||
                        on_error=self.link.callback(Msg::OnError)/>
 | 
					                        on_error=self.link.callback(Msg::OnError)/>
 | 
				
			||||||
                      {self.view_messages(error)}
 | 
					 | 
				
			||||||
                      {self.view_group_memberships(u)}
 | 
					                      {self.view_group_memberships(u)}
 | 
				
			||||||
                      <div>
 | 
					                      <div>
 | 
				
			||||||
                        <NavButton
 | 
					                        <NavButton
 | 
				
			||||||
@ -214,6 +213,7 @@ impl Component for UserDetails {
 | 
				
			|||||||
                            {"Change password"}
 | 
					                            {"Change password"}
 | 
				
			||||||
                        </NavButton>
 | 
					                        </NavButton>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
 | 
					                      {self.view_messages(error)}
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user