Compare commits

...

5 Commits

Author SHA1 Message Date
blek 6852536f39
add password field to /api/files/upload method 2023-10-27 20:12:08 +10:00
blek 04a40e26c4
add js UX for pass entry 2023-10-27 20:12:08 +10:00
blek 22444beba4
check for password server side 2023-10-27 20:12:08 +10:00
blek 5fc980a0b6
add password request on upload page 2023-10-27 20:12:08 +10:00
blek 6617efc9fe
add upload_pass config option 2023-10-27 20:12:00 +10:00
6 changed files with 115 additions and 1 deletions

View File

@ -21,6 +21,11 @@ allow_pass_protection=true
# This is shown only if allow_uploads = false # This is shown only if allow_uploads = false
# upload_disable_reason="File uploads were disabled because of an ongoing attack." # upload_disable_reason="File uploads were disabled because of an ongoing attack."
# If you want to restrict the uploads
# To only the people who have a password,
# uncomment this field
# upload_pass=super_secret_pass
# Timeout for deleting a user uploaded file # Timeout for deleting a user uploaded file
file_del_timeout=1800 file_del_timeout=1800

View File

@ -111,6 +111,10 @@ paths:
type: string type: string
example: binary file data example: binary file data
description: binary file data description: binary file data
instance_pass:
type: string
example: super_secret_pass
description: Instance-specific password needed to upload files
metadata: metadata:
type: object type: object
description: file info description: file info

View File

@ -22,6 +22,10 @@ pub struct FilesPolicy {
#[serde(default)] #[serde(default)]
pub upload_disable_reason: Option<String>, pub upload_disable_reason: Option<String>,
/// Upload password
#[serde(default)]
pub upload_pass: Option<String>,
/// Default time for file to be deleted /// Default time for file to be deleted
#[serde(default)] #[serde(default)]
pub file_del_timeout: usize, pub file_del_timeout: usize,
@ -42,6 +46,7 @@ impl Default for FilesPolicy {
allow_custom_names: true, allow_custom_names: true,
allow_pass_protection: true, allow_pass_protection: true,
upload_disable_reason: None, upload_disable_reason: None,
upload_pass: None,
file_del_timeout: 1800, file_del_timeout: 1800,
type_whitelist: None, type_whitelist: None,
type_blacklist: None, type_blacklist: None,

View File

@ -61,6 +61,7 @@ impl FormElement {
struct UploadFormData { struct UploadFormData {
filename: Option<String>, filename: Option<String>,
password: Option<String>, password: Option<String>,
instancepass: Option<String>,
lookup_kind: LookupKind, lookup_kind: LookupKind,
delmode: DeleteMode, delmode: DeleteMode,
file: Vec<u8>, file: Vec<u8>,
@ -73,6 +74,7 @@ impl Default for UploadFormData {
UploadFormData { UploadFormData {
filename: None, filename: None,
password: None, password: None,
instancepass: None,
lookup_kind: LookupKind::ByHash, lookup_kind: LookupKind::ByHash,
delmode: DeleteMode::Time, delmode: DeleteMode::Time,
file: vec![], file: vec![],
@ -127,6 +129,16 @@ impl UploadFormData {
} }
} }
match data.get("instancepass") {
Some(val) => {
let val = val.data.clone();
if let Ok(pass) = String::from_utf8(val) {
out.instancepass = Some(pass);
}
},
None => ()
};
let file = data.get("file")?; let file = data.get("file")?;
out.file = file.data.clone(); out.file = file.data.clone();
out.mime = file.mime.clone(); out.mime = file.mime.clone();
@ -201,6 +213,47 @@ pub async fn upload(form: FormData, ip: Option<IpAddr>, state: SharedState) -> R
) )
} }
if let Some(upload_pass) = state.config.files.upload_pass.clone() {
if let Some(pass) = formdata.instancepass {
if upload_pass != pass {
let error = ErrorPage {
env: state.env.clone(),
conf: state.config.clone(),
error_text: "Password is invalid".into(),
link: Some("/".into()),
link_text: Some("Go back".into())
};
return Ok(
Box::new(
html(
error.render()
.map_err(|x| HttpReject::AskamaError(x))?
)
)
)
}
} else {
let error = ErrorPage {
env: state.env.clone(),
conf: state.config.clone(),
error_text: "Password is not available".into(),
link: Some("/".into()),
link_text: Some("Go back".into())
};
return Ok(
Box::new(
html(
error.render()
.map_err(|x| HttpReject::AskamaError(x))?
)
)
)
}
}
let file = File::create( let file = File::create(
formdata.file, formdata.file,
formdata.mime, formdata.mime,

View File

@ -20,4 +20,8 @@
.alert.danger .alert-title { .alert.danger .alert-title {
background: #602020; background: #602020;
}
.alert.blue .alert-title {
background: #203050;
} }

View File

@ -9,6 +9,31 @@
{% endblock %} {% endblock %}
{% block scripts %}
{%- if conf.files.upload_pass.is_some() -%}
{#- Script to disable button when password is not entered -#}
{#- -#}<script>
{#- -#} (
{#- -#} ()=>{
{#- -#} const pass_inp=document.getElementById("instancepass");
{#- -#} const submit=document.getElementById("bfile-upload-submit");
{#- -#} submit.setAttribute('disabled',true);
{#- -#}
{#- -#} pass_inp.onchange=()=>{
{#- -#} if(pass_inp.value.length==0)
{#- -#} submit.setAttribute('disabled',true);
{#- -#} else submit.removeAttribute('disabled')
{#- -#} }
{#- -#} }
{#- -#} )()
{#- -#}</script>
{%- endif -%}
{% endblock %}
{% block body %} {% block body %}
<div style="max-width:95vw;width:fit-content;margin:0 auto"> <div style="max-width:95vw;width:fit-content;margin:0 auto">
@ -114,6 +139,24 @@
</p> </p>
</div> </div>
{%- else -%} {%- else -%}
{%- if let Some(pass) = conf.files.upload_pass -%}
<div class="alert blue">
<h1 class="alert-title">
Upload password
</h1>
<div class="alert-text">
<p>This instance requires a password to upload a file.</p>
<p>
<label>
Password:
<input type="password" name="instancepass" id="instancepass">
</label>
</p>
</div>
</div>
{%- endif -%}
<p> <p>
<input type="file" name="file" id="bfile-formupload-file" style="display: none" /> <input type="file" name="file" id="bfile-formupload-file" style="display: none" />
<label for="bfile-formupload-file"> <label for="bfile-formupload-file">
@ -142,7 +185,7 @@
</label> </label>
</p> </p>
<p> <p>
<button class='btn btn-fill'> <button class='btn btn-fill' id="bfile-upload-submit">
Upload! Upload!
</button> </button>
</p> </p>