Skip to content

Commit

Permalink
Improved password protection
Browse files Browse the repository at this point in the history
- Implemented password protection for removals
- Added success message on password-protected upload creation and editing for clarity
- Made auth page focus password field when it's left empty
  • Loading branch information
szabodanika committed Jul 10, 2023
1 parent 4372158 commit 05126fe
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 24 deletions.
75 changes: 75 additions & 0 deletions src/endpoints/auth_pasta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,78 @@ pub async fn auth_file_with_status(
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
}

#[get("/auth_remove_private/{id}")]
pub async fn auth_remove_private(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
// get access to the pasta collection
let mut pastas = data.pastas.lock().unwrap();

remove_expired(&mut pastas);

let intern_id = if ARGS.hash_ids {
hashid_to_u64(&id).unwrap_or(0)
} else {
to_u64(&id).unwrap_or(0)
};

for (_, pasta) in pastas.iter().enumerate() {
if pasta.id == intern_id {
return HttpResponse::Ok().content_type("text/html").body(
AuthPasta {
args: &ARGS,
id: id.into_inner(),
status: String::from(""),
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
encrypt_client: pasta.encrypt_client,
path: String::from("remove"),
}
.render()
.unwrap(),
);
}
}

HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
}

#[get("/auth_remove_private/{id}/{status}")]
pub async fn auth_remove_private_with_status(
data: web::Data<AppState>,
param: web::Path<(String, String)>,
) -> HttpResponse {
// get access to the pasta collection
let mut pastas = data.pastas.lock().unwrap();

remove_expired(&mut pastas);

let (id, status) = param.into_inner();

let intern_id = if ARGS.hash_ids {
hashid_to_u64(&id).unwrap_or(0)
} else {
to_u64(&id).unwrap_or(0)
};

for (_i, pasta) in pastas.iter().enumerate() {
if pasta.id == intern_id {
return HttpResponse::Ok().content_type("text/html").body(
AuthPasta {
args: &ARGS,
id,
status,
encrypted_key: pasta.encrypted_key.to_owned().unwrap_or_default(),
encrypt_client: pasta.encrypt_client,
path: String::from("remove"),
}
.render()
.unwrap(),
);
}
}

HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
}
20 changes: 14 additions & 6 deletions src/endpoints/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ pub async fn create(
}
}

let encrypt_server = new_pasta.encrypt_server;

pastas.push(new_pasta);

for (_, pasta) in pastas.iter().enumerate() {
Expand All @@ -286,10 +288,16 @@ pub async fn create(
to_animal_names(id)
};

Ok(HttpResponse::Found()
.append_header((
"Location",
format!("{}/pasta/{}", ARGS.public_path_as_str(), slug),
))
.finish())
if encrypt_server {
Ok(HttpResponse::Found()
.append_header(("Location", format!("/auth/{}/success", slug)))
.finish())
} else {
Ok(HttpResponse::Found()
.append_header((
"Location",
format!("{}/pasta/{}", ARGS.public_path_as_str(), slug),
))
.finish())
}
}
6 changes: 1 addition & 5 deletions src/endpoints/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,7 @@ pub async fn post_submit_edit_private(
return Ok(HttpResponse::Found()
.append_header((
"Location",
format!(
"{}/pasta/{}",
ARGS.public_path_as_str(),
pastas[index].id_as_animals()
),
format!("/auth/{}/success", pastas[index].id_as_animals()),
))
.finish());
}
Expand Down
126 changes: 124 additions & 2 deletions src/endpoints/remove.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use actix_web::{get, web, HttpResponse};
use actix_multipart::Multipart;
use actix_web::{get, post, web, Error, HttpResponse};
use futures::TryStreamExt;

use crate::args::ARGS;
use crate::endpoints::errors::ErrorTemplate;
use crate::pasta::PastaFile;
use crate::util::animalnumbers::to_u64;
use crate::util::db::delete;
use crate::util::hashids::to_u64 as hashid_to_u64;
use crate::util::misc::remove_expired;
use crate::util::misc::{decrypt, remove_expired};
use crate::AppState;
use askama::Template;
use std::fs;
Expand All @@ -29,6 +31,16 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes

for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id {
// if it's encrypted or read-only, it needs password to be deleted
if pasta.encrypt_server || pasta.readonly {
return HttpResponse::Found()
.append_header((
"Location",
format!("/auth_remove_private/{}", pasta.id_as_animals()),
))
.finish();
}

// remove the file itself
if let Some(PastaFile { name, .. }) = &pasta.file {
if fs::remove_file(format!(
Expand Down Expand Up @@ -74,3 +86,113 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
}

#[post("/remove/{id}")]
pub async fn post_remove(
data: web::Data<AppState>,
id: web::Path<String>,
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
if ARGS.readonly {
return Ok(HttpResponse::Found()
.append_header(("Location", format!("{}/", ARGS.public_path_as_str())))
.finish());
}

let id = if ARGS.hash_ids {
hashid_to_u64(&id).unwrap_or(0)
} else {
to_u64(&id.into_inner()).unwrap_or(0)
};

let mut pastas = data.pastas.lock().unwrap();

remove_expired(&mut pastas);

let mut password = String::from("");

while let Some(mut field) = payload.try_next().await? {
if field.name() == "password" {
while let Some(chunk) = field.try_next().await? {
password = std::str::from_utf8(&chunk).unwrap().to_string();
}
}
}

for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id {
if pastas[i].readonly || pastas[i].encrypt_server {
if password != *"" {
let res = decrypt(pastas[i].encrypted_key.as_ref().unwrap(), &password);
if res.is_ok() {
// remove the file itself
if let Some(PastaFile { name, .. }) = &pasta.file {
if fs::remove_file(format!(
"./{}/attachments/{}/{}",
ARGS.data_dir,
pasta.id_as_animals(),
name
))
.is_err()
{
log::error!("Failed to delete file {}!", name)
}

// and remove the containing directory
if fs::remove_dir(format!(
"./{}/attachments/{}/",
ARGS.data_dir,
pasta.id_as_animals()
))
.is_err()
{
log::error!("Failed to delete directory {}!", name)
}
}

// remove it from in-memory pasta list
pastas.remove(i);

delete(Some(&pastas), Some(id));

return Ok(HttpResponse::Found()
.append_header((
"Location",
format!("{}/pastalist", ARGS.public_path_as_str()),
))
.finish());
} else {
return Ok(HttpResponse::Found()
.append_header((
"Location",
format!("/auth_remove_private/{}/incorrect", pasta.id_as_animals()),
))
.finish());
}
} else {
return Ok(HttpResponse::Found()
.append_header((
"Location",
format!("/auth_remove_private/{}/incorrect", pasta.id_as_animals()),
))
.finish());
}
}

return Ok(HttpResponse::Found()
.append_header((
"Location",
format!(
"{}/pasta/{}",
ARGS.public_path_as_str(),
pastas[i].id_as_animals()
),
))
.finish());
}
}

Ok(HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
}
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,17 @@ async fn main() -> std::io::Result<()> {
.service(create::index)
.service(guide::guide)
.service(auth_admin::auth_admin)
.service(auth_pasta::auth_file_with_status)
.service(auth_admin::auth_admin_with_status)
.service(auth_pasta::auth_pasta_with_status)
.service(auth_pasta::auth_raw_pasta_with_status)
.service(auth_pasta::auth_edit_private_with_status)
.service(auth_pasta::auth_remove_private_with_status)
.service(auth_pasta::auth_file)
.service(auth_pasta::auth_pasta)
.service(auth_pasta::auth_raw_pasta)
.service(auth_pasta::auth_edit_private)
.service(auth_pasta::auth_file_with_status)
.service(auth_pasta::auth_remove_private)
.service(pasta_endpoint::getpasta)
.service(pasta_endpoint::postpasta)
.service(pasta_endpoint::getshortpasta)
Expand All @@ -131,6 +133,7 @@ async fn main() -> std::io::Result<()> {
.default_service(web::route().to(errors::not_found))
.wrap(middleware::Logger::default())
.service(remove::remove)
.service(remove::post_remove)
.service(pastalist::list)
.wrap(Condition::new(
ARGS.auth_basic_username.is_some()
Expand Down
51 changes: 45 additions & 6 deletions templates/auth_pasta.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
{% if encrypt_client %}

<form id="auth-form" method="POST" action="/{{path}}/{{id}}" enctype="multipart/form-data">
{% if status == "success" %}
<b>
Success!
</b> <br>
{% endif %}
<label for="password"> Please enter the password to open the upload. <sup>
<a href="/guide#encryption"></a></sup></label>
<input id="password-field" placeholder="Password" type="password" autocomplete="off">
<input id="password-field" required placeholder="Password" type="password" autocomplete="off">
<input id="password-hidden" name="password" type="hidden">
<button>Open</button>

{% if status == "incorrect" %}
<p>
<b>
Incorrect password.
</p>
</b>
{% endif %}
</form>

Expand All @@ -23,11 +28,18 @@
const passwordHiddenField = document.getElementById("password-hidden");

form.onsubmit = function () {

if (passwordField.value.trim() == "") {
passwordField.focus();
return false;
}

let key = decryptWithPassword(passwordField.value, "{{ encrypted_key }}");

if (key) {
passwordHiddenField.value = key;
}

};

function decryptWithPassword(password, encryptedHex) {
Expand All @@ -48,20 +60,47 @@
{% else %}

<form id="auth-form" method="POST" action="/{{path}}/{{id}}" enctype="multipart/form-data">
{% if status == "success" %}
<b>
Success!
</b> <br>
{% endif %}
<label for="password" style="margin-bottom: 0.5rem;"> Please enter the
password to access or modify this upload. <sup>
<a href="/guide#encryption"></a></sup></label>
<input id="password-field" placeholder="Password" name="password" type="password" autocomplete="off" />
<button>Okay</button>

{% if status == "incorrect" %}
<p>
<b>
Incorrect password.
</p>
</b>
{% endif %}

</form>


<script>

const form = document.getElementById("auth-form");
const passwordField = document.getElementById("password-field");

form.onsubmit = function () {

if (passwordField.value.trim() == "") {
passwordField.focus();
return false;
}

let key = decryptWithPassword(passwordField.value, "{{ encrypted_key }}");

if (key) {
passwordHiddenField.value = key;
}

};

</script>

{% endif %}

{% include "footer.html" %} {% if !args.pure_html %}
Expand Down

0 comments on commit 05126fe

Please sign in to comment.