lldap/app/src/login.rs

166 lines
5.6 KiB
Rust
Raw Normal View History

2021-05-13 17:33:57 +00:00
use crate::api::HostService;
2021-08-26 19:56:42 +00:00
use anyhow::{anyhow, Context, Result};
use lldap_auth::*;
2021-05-13 17:33:57 +00:00
use wasm_bindgen::JsCast;
use yew::prelude::*;
use yew::services::{fetch::FetchTask, ConsoleService};
use yew::FocusEvent;
2021-05-13 17:33:57 +00:00
pub struct LoginForm {
link: ComponentLink<Self>,
on_logged_in: Callback<(String, bool)>,
2021-05-13 17:33:57 +00:00
error: Option<anyhow::Error>,
node_ref: NodeRef,
login_start: Option<opaque::client::login::ClientLogin>,
2021-05-14 07:28:15 +00:00
// Used to keep the request alive long enough.
_task: Option<FetchTask>,
2021-05-13 17:33:57 +00:00
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
pub on_logged_in: Callback<(String, bool)>,
2021-05-13 17:33:57 +00:00
}
pub enum Msg {
Submit,
2021-06-23 08:57:34 +00:00
AuthenticationStartResponse(Result<Box<login::ServerLoginStartResponse>>),
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(),
)
2021-05-13 17:33:57 +00:00
}
impl LoginForm {
fn set_error(&mut self, error: anyhow::Error) {
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 {
Msg::Submit => {
let username = get_form_field("username")
2021-06-23 08:57:34 +00:00
.ok_or_else(|| anyhow!("Could not get username from form"))?;
let password = get_form_field("password")
2021-06-23 08:57:34 +00:00
.ok_or_else(|| anyhow!("Could not get password from form"))?;
let mut rng = rand::rngs::OsRng;
2021-08-26 19:56:42 +00:00
let login_start_request = opaque::client::login::start_login(&password, &mut rng)
.context("Could not initialize login")?;
self.login_start = Some(login_start_request.state);
let req = login::ClientLoginStartRequest {
username,
login_start_request: login_start_request.message,
};
self.call_backend(
HostService::login_start,
req,
Msg::AuthenticationStartResponse,
)?;
Ok(())
}
Msg::AuthenticationStartResponse(Ok(res)) => {
debug_assert!(self.login_start.is_some());
let login_finish = match opaque::client::login::finish_login(
self.login_start.as_ref().unwrap().clone(),
res.credential_response,
) {
Err(e) => {
// Common error, we want to print a full error to the console but only a
// simple one to the user.
ConsoleService::error(&format!("Invalid username or password: {}", e));
self.error = Some(anyhow!("Invalid username or password"));
return Ok(());
}
Ok(l) => l,
};
let req = login::ClientLoginFinishRequest {
server_data: res.server_data,
credential_finalization: login_finish.message,
};
self.call_backend(
HostService::login_finish,
req,
Msg::AuthenticationFinishResponse,
)?;
Ok(())
}
Msg::AuthenticationStartResponse(Err(e)) => Err(anyhow!(
"Could not log in (invalid response to login start): {}",
e
)),
Msg::AuthenticationFinishResponse(Ok(user_info)) => {
self.on_logged_in.emit(user_info);
Ok(())
}
Msg::AuthenticationFinishResponse(Err(e)) => Err(anyhow!("Could not log in: {}", e)),
}
}
}
2021-05-13 17:33:57 +00:00
impl Component for LoginForm {
type Message = Msg;
type Properties = Props;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
LoginForm {
2021-05-30 15:07:34 +00:00
link,
2021-05-13 17:33:57 +00:00
on_logged_in: props.on_logged_in,
error: None,
node_ref: NodeRef::default(),
login_start: None,
2021-05-14 07:28:15 +00:00
_task: None,
2021-05-13 17:33:57 +00:00
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.error = None;
if let Err(e) = self.handle_message(msg) {
self.set_error(e);
}
2021-05-13 17:33:57 +00:00
true
}
fn change(&mut self, _: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<form ref=self.node_ref.clone() onsubmit=self.link.callback(|e: FocusEvent| { e.prevent_default(); Msg::Submit })>
2021-05-13 17:33:57 +00:00
<div>
<label for="username">{"User name:"}</label>
<input type="text" id="username" />
</div>
<div>
<label for="password">{"Password:"}</label>
<input type="password" id="password" />
</div>
<button type="submit">{"Login"}</button>
2021-05-13 17:33:57 +00:00
<div>
{ if let Some(e) = &self.error {
html! { e.to_string() }
} else { html! {} }
}
</div>
</form>
}
}
}