use argon2::password_hash::{PasswordHasher, SaltString};
use argon2::Argon2;
use gloo::console::log;
use rand_core::OsRng;
use web_sys::HtmlInputElement;
use yew::prelude::*;
fn hash_password(pw: &str) -> String {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
argon2
.hash_password(pw.as_bytes(), &salt)
.expect("failed to hash pw")
.to_string()
}
enum Msg {
NoOp,
SetEmail(String),
SetPassword(String),
SetUsername(String),
UpdateCredentials,
}
#[derive(Default)]
struct App {
email: Option<String>,
password: Option<String>,
username: Option<String>,
hashed: Option<String>,
}
impl App {
fn whats_missing(&self) -> Option<String> {
let mut missing = vec![];
if self.username.is_none() {
missing.push("username");
}
if self.email.is_none() {
missing.push("email");
}
if self.password.is_none() {
missing.push("password");
}
match missing.len() {
0 => None,
1 => Some(missing[0].to_string()),
2 => Some(format!("{} and {}", missing[0], missing[1])),
3 => Some(format!("{}, {} and {}", missing[0], missing[1], missing[2])),
_ => unreachable!(),
}
}
fn update_credentials(&mut self) {
if self.password.is_none() {
log!("error: password unset, but credentials requested");
return;
}
let pw = self.password.as_ref().unwrap();
let hashed = hash_password(pw);
log!("hashed password to", &hashed);
self.hashed = Some(hashed);
}
fn display_credentials(&self) -> Html {
if let (Some(username), Some(email), Some(hash)) =
(&self.username, &self.email, &self.hashed)
{
html! {
<>
<hr />
<p>{"Your credentials are as follows: "}</p>
<pre>
{" {\n"}
{" username = \""}{username}{"\";\n"}
{" email = \""}{email}{"\";\n"}
{" password = \"{ARGON2}"}{hash}{"\";\n"}
{" }"}
</pre>
<p>
{"Please propose a CL to "}
<a href="https://at.tvl.fyi/?q=//ops/users/default.nix">
<code>{"//ops/users/default.nix"}</code>
</a>
{", or submit your patch via email to "}
<a href="mailto:depot@tvl.su">{"depot@tvl.su"}</a>
{"."}
</p>
</>
}
} else {
html! {}
}
}
}
fn input_to_message(event: InputEvent, msg: fn(String) -> Msg) -> Msg {
let input = event.target_unchecked_into::<HtmlInputElement>();
if input.check_validity() {
msg(input.value())
} else {
Msg::NoOp
}
}
fn set_if_present(s: String, target: &mut Option<String>) {
if s.is_empty() {
*target = None;
} else {
*target = Some(s);
}
}
impl Component for App {
type Message = Msg;
type Properties = ();
fn create(_: &Context<Self>) -> Self {
Self::default()
}
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
log!(
"handling message ",
match msg {
Msg::NoOp => "NoOp",
Msg::SetEmail(_) => "SetEmail",
Msg::SetUsername(_) => "SetUsername",
Msg::SetPassword(_) => "SetPassword",
Msg::UpdateCredentials => "UpdateCredentials",
}
);
match msg {
Msg::NoOp => return false,
Msg::SetEmail(email) => set_if_present(email, &mut self.email),
Msg::SetUsername(username) => set_if_present(username, &mut self.username),
Msg::SetPassword(password) => set_if_present(password, &mut self.password),
Msg::UpdateCredentials => {
self.update_credentials();
}
}
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
let link = ctx.link();
include!("main.html")
}
}
fn main() {
yew::Renderer::<App>::new().render();
}