upload: Require POST for token verification

In order to mitigate against MUAs previewing URLs, move the token
verification flow to a POST handler, and add a new GET handler
which returns a form requiring the user to click an additional time
in order to verify their address.

The returned form also carries some JavaScript which will attempt to
do this for the user, meaning the experience for the user should be
almost exactly as before, while mitigating MUA previews.

Closes: #53

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
This commit is contained in:
Daniel Silverstone 2019-09-26 21:04:48 +02:00
parent 6b7cbbe1c1
commit 6df212f087
No known key found for this signature in database
GPG Key ID: C30DF439F2987D74
5 changed files with 69 additions and 3 deletions

34
dist/assets/js/upload-verify.js vendored Normal file
View File

@ -0,0 +1,34 @@
const container = document.getElementById("container");
const postform = document.getElementById("postform");
const pleasewait = document.getElementById("pleasewait");
const failed = document.getElementById("failed");
const form = document.getElementById("postform");
const url = form.getAttribute("action");
fetch(url, { method: "POST" })
.then(result => result.text())
.then(body => {
const frag = document.createElement("div");
frag.innerHTML = body;
container.appendChild(frag);
const result = document.getElementById("verification-result");
if (result !== null) {
while (result.firstChild) {
container.appendChild(result.firstChild);
}
container.removeChild(frag);
} else {
// Leave the full content appended since it'll likely be plain text
}
// Hide the pleasewait too
pleasewait.style.display = "none";
})
.catch(err => {
// On error, hide the 'please wait' and show the 'Something went wrong'
pleasewait.style.display = "none";
failed.textContent = failed.textContent + err;
failed.style.display = "block";
});
// Hide the form and display the 'please wait' block
postform.style.display = "none";
pleasewait.style.display = "block";

View File

@ -1,5 +1,5 @@
{{#> layout}}
<div class="row">
<div class="row" id="verification-result">
{{#if verified }}
<p>
Your key

View File

@ -0,0 +1,18 @@
{{#> layout}}
<div class="row" id="container">
<form method="POST" action="/verify/{{token}}" id="postform">
<p>
Please click here to complete the verification process:
<input type="submit" value="Validate"/>
</p>
</form>
<p style="display: none" id="pleasewait">
Please wait while your email address is verified…
</p>
<p style="display: none" id="failed">
Something went wrong:
</p>
</div>
<script src="/assets/js/upload-verify.js" type="text/javascript"></script>
{{/layout}}

View File

@ -364,6 +364,7 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
vks_web::request_verify_form,
vks_web::request_verify_form_data,
vks_web::verify_confirm,
vks_web::verify_confirm_form,
vks_web::quick_upload,
vks_web::quick_upload_proceed,
// Debug
@ -1095,7 +1096,7 @@ pub mod tests {
let pattern = format!("{}(/verify/[^ \t\n]*)", BASE_URI);
let confirm_uri = pop_mail_capture_pattern(filemail_path, &pattern);
let response = client.get(&confirm_uri).dispatch();
let response = client.post(&confirm_uri).dispatch();
assert_eq!(response.status(), Status::Ok);
}

View File

@ -37,6 +37,11 @@ mod forms {
}
mod template {
#[derive(Serialize)]
pub struct VerifyForm {
pub token: String,
}
#[derive(Serialize)]
pub struct Verify {
pub verified: bool,
@ -437,7 +442,7 @@ pub fn request_verify_form_data(
MyResponse::upload_response(result)
}
#[get("/verify/<token>")]
#[post("/verify/<token>")]
pub fn verify_confirm(
db: rocket::State<KeyDatabase>,
token_service: rocket::State<StatefulTokens>,
@ -461,3 +466,11 @@ pub fn verify_confirm(
}
}
#[get("/verify/<token>")]
pub fn verify_confirm_form(
token: String,
) -> MyResponse {
MyResponse::ok("upload/verification-form", template::VerifyForm {
token
})
}