Skip to content

Commit

Permalink
fix: Implement password manager best practices. (#95)
Browse files Browse the repository at this point in the history
* Implement password manager best practices.

* Remove hcaptcha js when not in use.

* fix: Run strength check on all input.
  • Loading branch information
9876691 authored Feb 28, 2023
1 parent 0ded67d commit 0f725f0
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"bungcip.better-toml",
"vadimcn.vscode-lldb",
"mutantdino.resourcemonitor",
"matklad.rust-analyzer",
"rust-lang.rust-analyzer",
"zxh404.vscode-proto3"
],

Expand Down
4 changes: 2 additions & 2 deletions crates/actix-server/src/auth/change_password.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ markup::define! {

h1 { "Change Password" }

@forms::PasswordInput{ title: "Password", name: "password", value: &form.password, help_text: "", errors }
@forms::PasswordInput{ title: "Confirm Password", name: "confirm_password", value: &form.confirm_password, help_text: "", errors }
@forms::PasswordInput{ title: "Password", name: "password", value: &form.password, autocomplete: "new-password", help_text: "", errors }
@forms::PasswordInput{ title: "Confirm Password", name: "confirm_password", value: &form.confirm_password, autocomplete: "new-password", help_text: "", errors }

input[type="hidden", value=&form.reset_password_selector, name="reset_password_selector"] {}
input[type="hidden", value=&form.reset_password_validator, name="reset_password_validator"] {}
Expand Down
4 changes: 2 additions & 2 deletions crates/actix-server/src/auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ markup::define! {

h1 { "Sign In" }

@forms::EmailInput{ title: "Email", name: "email", value: &form.email, help_text: "", errors }
@forms::PasswordInput{ title: "Password", name: "password", value: &form.password, help_text: "", errors }
@forms::EmailInput{ title: "Email", name: "email", value: &form.email, autocomplete: "current-password", help_text: "", errors }
@forms::PasswordInput{ title: "Password", name: "password", value: &form.password, autocomplete: "current-password", help_text: "", errors }

@if let Some(hcaptcha_config) = hcaptcha_config {
button.a_button.success."h-captcha"[
Expand Down
23 changes: 20 additions & 3 deletions crates/actix-server/src/auth/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,26 @@ markup::define! {
errors: &'a ValidationErrors) {
form.m_authentication[id="auth-form", method = "post"] {
h1 { "Register" }
@forms::EmailInput{ title: "Email", name: "email", value: &form.email, help_text: "", errors }
@forms::PasswordInput{ title: "Password", name: "password", value: &form.password, help_text: "", errors }
@forms::PasswordInput{ title: "Confirm Password", name: "confirm_password", value: &form.confirm_password, help_text: "", errors }
@forms::EmailInput{
title: "Email",
name: "email",
autocomplete: "username",
value: &form.email,
help_text: "",
errors
}
@forms::PasswordInput{
title: "Password",
name: "password",
value: &form.password,
autocomplete: "new-password",
help_text: "", errors }
@forms::PasswordInput{
title: "Confirm Password",
name: "confirm_password",
value: &form.confirm_password,
autocomplete: "new-password",
help_text: "", errors }

@if let Some(hcaptcha_config) = hcaptcha_config {
button.a_button.success."h-captcha"[
Expand Down
2 changes: 1 addition & 1 deletion crates/actix-server/src/auth/reset_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ markup::define! {

h1 { "Password Reset Request" }

@forms::EmailInput{ title: "Email", name: "email", value: &form.email, help_text: "", errors }
@forms::EmailInput{ title: "Email", name: "email", value: &form.email, autocomplete: "username", help_text: "", errors }

@if let Some(hcaptcha) = hcaptcha_config {
div."h-captcha"["data-sitekey"=&hcaptcha.hcaptcha_site_key] {}
Expand Down
24 changes: 12 additions & 12 deletions crates/actix-server/src/components/forms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ pub fn escape(src: &str) -> String {

markup::define! {
PlaceholderInput<'a>(title: &'a str, name: &'a str, value: &'a str, input_type: &'a str,
help_text: &'a str, placeholder: &'a str, errors: &'a ValidationErrors) {
help_text: &'a str, placeholder: &'a str, autocomplete: &'a str, errors: &'a ValidationErrors) {
label[for=name] { {title} }
@if !filter_errors(name, errors).is_empty() {
input.error[id=name, name=name, placeholder=placeholder, value=value, type=input_type] {}
input.error[id=name, name=name, placeholder=placeholder, autocomplete=autocomplete, value=value, type=input_type] {}
} else {
input[id=name, name=name, placeholder=placeholder, value=value, type=input_type] {}
input[id=name, name=name, placeholder=placeholder, autocomplete=autocomplete, value=value, type=input_type] {}
}
@for error in &filter_errors(name, errors) {
span.error {
Expand All @@ -49,25 +49,25 @@ markup::define! {
}
span.a_help_text { {help_text} }
}
Input<'a>(title: &'a str, name: &'a str, value: &'a str, input_type: &'a str,
Input<'a>(title: &'a str, name: &'a str, value: &'a str, input_type: &'a str, autocomplete: &'a str,
help_text: &'a str, errors: &'a ValidationErrors) {
{ PlaceholderInput{ title, name, value, input_type, help_text, placeholder: "", errors} }
{ PlaceholderInput{ title, name, value, input_type, help_text, placeholder: "", autocomplete, errors} }
}
EmailInput<'a>(title: &'a str, name: &'a str, value: &'a str, help_text: &'a str, errors: &'a ValidationErrors) {
{ Input{ title, name, value, input_type: "email", help_text, errors } }
EmailInput<'a>(title: &'a str, name: &'a str, value: &'a str, autocomplete: &'a str, help_text: &'a str, errors: &'a ValidationErrors) {
{ Input{ title, name, value, input_type: "email", autocomplete, help_text, errors } }
}
TextInput<'a>(title: &'a str, name: &'a str, value: &'a str, help_text: &'a str, errors: &'a ValidationErrors) {
{ Input{ title, name, value, input_type: "text", help_text, errors } }
{ Input{ title, name, value, input_type: "text", autocomplete: "", help_text, errors } }
}
PasswordInput<'a>(title: &'a str, name: &'a str, value: &'a str, help_text: &'a str, errors: &'a ValidationErrors) {
{ Input{ title, name, value, input_type: "password", help_text, errors } }
PasswordInput<'a>(title: &'a str, name: &'a str, value: &'a str, autocomplete: &'a str, help_text: &'a str, errors: &'a ValidationErrors) {
{ Input{ title, name, value, input_type: "password", autocomplete, help_text, errors } }
}
DateInput<'a>(title: &'a str, name: &'a str, value: &'a str, help_text: &'a str, errors: &'a ValidationErrors) {
{ Input{ title, name, value, input_type: "date", help_text, errors } }
{ Input{ title, name, value, input_type: "date", autocomplete: "", help_text, errors } }
}
SearchInput<'a>(title: &'a str, name: &'a str, value: &'a str, help_text: &'a str, placeholder: &'a str) {
{ PlaceholderInput{ title, name, value, input_type: "text",
help_text, placeholder, errors: &ValidationErrors::default() } }
help_text, placeholder, autocomplete: "", errors: &ValidationErrors::default() } }
}

TextArea<'a>(title: &'a str, name: &'a str, value: &'a str, help_text: &'a str, placeholder: &'a str, errors: &'a ValidationErrors) {
Expand Down
19 changes: 16 additions & 3 deletions crates/actix-server/src/encrypted_auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,27 @@ markup::define! {

label[for="email"] { "Email" }
@if errors.is_none() {
input[id="email", name = "email", value = forms::escape(&form.email), "data-target" = "login.email"] {}
input[id="email",
name = "email",
autocomplete= "username",
value = forms::escape(&form.email),
"data-target" = "login.email"] {}
} else {
input.error[id="email", name = "email", type="email", value = &form.email, "data-target" = "login.email"] {}
input.error[id="email",
name = "email",
type="email",
autocomplete= "username",
value = &form.email,
"data-target" = "login.email"] {}
span.error { "Invalid email or password" }
}

label[for="password"] { "Password" }
input[id="password", name = "password", type="password", "data-target" = "login.password"] {}
input[id="password",
name = "password",
autocomplete= "current-password",
type="password",
"data-target" = "login.password"] {}

button.a_button.success[type = "submit",
"data-target" = "login.button",
Expand Down
28 changes: 22 additions & 6 deletions crates/actix-server/src/encrypted_auth/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub struct Registration {
pub async fn registration(config: web::Data<config::Config>) -> Result<HttpResponse> {
let body = RegistrationPage {
form: &Registration::default(),
hcaptcha_enabled: config.hcaptcha_config.is_some()
};

Ok(layouts::session_layout(
Expand Down Expand Up @@ -148,6 +149,7 @@ pub async fn process_registration(
Err(_) => {
let body = RegistrationPage {
form: &registration,
hcaptcha_enabled: config.hcaptcha_config.is_some()
};

Ok(layouts::session_layout(
Expand All @@ -160,26 +162,38 @@ pub async fn process_registration(
}

markup::define! {
RegistrationPage<'a>(form: &'a Registration) {
RegistrationPage<'a>(form: &'a Registration, hcaptcha_enabled: bool) {
div["data-controller" = "registration password"] {
form.m_authentication {

h1 { "Register" }

label[for="email"] { "Email" }
input[id="email", name = "email", type="email", value = forms::escape(&form.email), "data-target" = "registration.email"] {}
input[id="email",
name = "email",
type="email",
autocomplete="username",
value = forms::escape(&form.email),
"data-target" = "registration.email"] {}
span.a_help_text { "You'll use your email address to log in." }

label[for="password"] { "Master Password" }
input[id="password", name="password", type="password",
"data-action"="keyup->password#keyPress",
input[id="password",
name="password",
type="password",
autocomplete="new-password",
"data-action"="input->password#keyPress",
"data-target" = "registration.password password.password"] {}
span.a_help_text["data-target" = "password.help"] { "The master password is the password we use to generate your private keys. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." }
span.a_help_text["data-target" = "password.warning"] {}
span.a_help_text["data-target" = "password.suggestions"] {}

label[for="confirm_password"] { "Re-type Master Password" }
input[id="confirm_password", name="confirm_password", type="password", "data-target" = "registration.confirmPassword"] {}
input[id="confirm_password",
name="confirm_password",
type="password",
autocomplete="new-password",
"data-target" = "registration.confirmPassword"] {}

button.a_button.success[type = "submit", "data-target" = "registration.button password.button",
"data-action" = "registration#register"] { "Sign Up" }
Expand Down Expand Up @@ -208,6 +222,8 @@ markup::define! {
type="hidden"] {}
}
}
script[src="https://hcaptcha.com/1/api.js", async="async", defer="defer"] {}
@if *hcaptcha_enabled {
script[src="https://hcaptcha.com/1/api.js", async="async", defer="defer"] {}
}
}
}
13 changes: 12 additions & 1 deletion crates/actix-server/src/static_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ static FAR: Duration = Duration::from_secs(180 * 24 * 60 * 60);

pub async fn static_file(path: Path<String>) -> HttpResponse {
let name = &path.into_inner();
if let Some(data) = assets::files::StaticFile::get(name) {

dbg!(&name);

let data = if name == "index.css.map" {
Some(&assets::files::index_css_map)
} else if name == "index.js.map" {
Some(&assets::files::index_js_map)
} else {
assets::files::StaticFile::get(&name)
};

if let Some(data) = data {
let far_expires = SystemTime::now() + FAR;
HttpResponse::Ok()
.insert_header(Expires(far_expires.into()))
Expand Down
2 changes: 1 addition & 1 deletion crates/asset-pipeline/controllers/password_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class extends Controller {
}

async keyPress(event: InputEvent) {
const result = zxcvbn(this.passwordTarget.value)
const result = await zxcvbn(this.passwordTarget.value)
if(result.feedback) {
const feedback: FeedbackType = result.feedback
if(feedback.warning != '') {
Expand Down

0 comments on commit 0f725f0

Please sign in to comment.