(barely) working /manage endpoint
This commit is contained in:
parent
440bf662fd
commit
2b1722c540
|
@ -16,7 +16,7 @@ use {Error, Result};
|
||||||
/// - Lower-case the whole thing using the empty locale
|
/// - Lower-case the whole thing using the empty locale
|
||||||
///
|
///
|
||||||
/// See https://autocrypt.org/level1.html#e-mail-address-canonicalization
|
/// See https://autocrypt.org/level1.html#e-mail-address-canonicalization
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Email(String);
|
pub struct Email(String);
|
||||||
|
|
||||||
impl Email {
|
impl Email {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang=en>
|
||||||
|
<head>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>Please confirm deletion of your key</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Someone, presumably you, requested that your key will no longer
|
||||||
|
be available when searching for you email addresses on
|
||||||
|
<a href="{{base_uri}}">{{domain}}</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If it was not you, please ignore this email.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To de-list your key on <a href="{{base_uri}}">{{domain}}</a>,
|
||||||
|
follow this link:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="{{base_uri}}{{uri}}">{{base_uri}}{{uri}}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you do not want to de-list your key, please ignore this
|
||||||
|
email.
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,10 @@
|
||||||
|
Someone, presumably you, requested that your key will no longer be
|
||||||
|
available when searching for you email addresses on {{domain}}.
|
||||||
|
|
||||||
|
If it was not you, please ignore this email.
|
||||||
|
|
||||||
|
To de-list your key on {{domain}}, follow this link:
|
||||||
|
|
||||||
|
{{base_uri}}{{uri}}
|
||||||
|
|
||||||
|
If you do not want to de-list your key, please ignore this email.
|
|
@ -1,7 +1,7 @@
|
||||||
{{#> layout }}
|
{{#> layout }}
|
||||||
{{> search-form}}
|
{{> search-form}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<p>You can also <a href="/publish">upload</a> or <a href="/delete">delete</a> your key.</p>
|
<p>You can also <a href="/publish">upload</a> or <a href="/vks/manage">delete</a> your key.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{{#> layout }}
|
||||||
|
<div class="row">
|
||||||
|
<form action="/vks/manage" method="POST">
|
||||||
|
<div class="manage">
|
||||||
|
<input type="text" name="search_term" class="manageEmail" autofocus
|
||||||
|
placeholder="Your Email / Key ID / Fingerprint">
|
||||||
|
<button type="submit" class="manageButton button">
|
||||||
|
Manage
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<p>Please enter one of your email addesses.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="text-align: left;">
|
||||||
|
<strong><a href="/">« Back</a></strong>
|
||||||
|
</div>
|
||||||
|
{{/layout}}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{{#> layout }}
|
||||||
|
<div class="row">
|
||||||
|
<p>This management link is invalid or expired.
|
||||||
|
<p>You can <a href="/vks/manage">click here</a> to request a new one.
|
||||||
|
</div>
|
||||||
|
{{/layout}}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{{#> layout }}
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
{{#if uid_unpublished}}
|
||||||
|
<div class="flash">Unpublished address: <tt>{{uid_unpublished}}</tt></div>
|
||||||
|
{{/if}}
|
||||||
|
<div style="width: 80%; margin-left: auto; margin-right: auto; text-align: left;">
|
||||||
|
{{#each uid_status}}
|
||||||
|
<div>
|
||||||
|
<div style="float: right;">
|
||||||
|
<form action="/vks/manage/unpublish" method="post">
|
||||||
|
<input type="hidden" name="token" value="{{../token}}" />
|
||||||
|
<input type="hidden" name="address" value="{{address}}" />
|
||||||
|
<i class="fa fa-eye-slash"></i> <input type="submit" class="link" value="Unpublish">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{{#if published}}
|
||||||
|
<i class="fa fa-envelope"></i> <tt>{{address}}</tt>
|
||||||
|
{{else}}
|
||||||
|
<tt>{{address}}</tt>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/layout}}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{{#> layout }}
|
||||||
|
<p>
|
||||||
|
We have sent an email with further instructions to <tt>{{address}}</tt>.
|
||||||
|
</p>
|
||||||
|
<div style="text-align: left;">
|
||||||
|
<strong><a href="/">« Back</a></strong>
|
||||||
|
</div>
|
||||||
|
{{/layout}}
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<p>You can delete an uploaded key <a href="/delete">here</a>.</p>
|
<p>You can delete an uploaded key <a href="/vks/manage">here</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
18
src/mail.rs
18
src/mail.rs
|
@ -23,7 +23,7 @@ mod context {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct Deletion {
|
pub struct Manage {
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
pub base_uri: String,
|
pub base_uri: String,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
|
@ -92,6 +92,22 @@ impl Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_manage_token(&self, recipients: &[Email], uri: &str)
|
||||||
|
-> Result<()> {
|
||||||
|
let ctx = context::Manage {
|
||||||
|
uri: uri.to_string(),
|
||||||
|
base_uri: self.base_uri.clone(),
|
||||||
|
domain: self.domain.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send(
|
||||||
|
recipients,
|
||||||
|
&format!("{}: Manage your key", &self.domain),
|
||||||
|
"manage",
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn send<T>(&self, to: &[Email], subject: &str, template: &str, ctx: T)
|
fn send<T>(&self, to: &[Email], subject: &str, template: &str, ctx: T)
|
||||||
-> Result<()>
|
-> Result<()>
|
||||||
where T: Serialize + Clone,
|
where T: Serialize + Clone,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use sealed_state::SealedState;
|
||||||
|
|
||||||
use database::types::{Fingerprint};
|
use database::types::{Fingerprint};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use Result;
|
||||||
|
|
||||||
const REVISION: u8 = 1;
|
const REVISION: u8 = 1;
|
||||||
|
|
||||||
|
@ -36,15 +37,17 @@ impl Service {
|
||||||
base64::encode_config(&token_sealed, base64::URL_SAFE_NO_PAD)
|
base64::encode_config(&token_sealed, base64::URL_SAFE_NO_PAD)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(&self, token_encoded: &str) -> Result<Fingerprint, String> {
|
pub fn check(&self, token_encoded: &str) -> Result<Fingerprint> {
|
||||||
let token_sealed = base64::decode_config(&token_encoded, base64::URL_SAFE_NO_PAD).map_err(|_| "invalid b64")?;
|
let token_sealed = base64::decode_config(&token_encoded, base64::URL_SAFE_NO_PAD)
|
||||||
let token_str = self.sealed_state.unseal(token_sealed)?;
|
.map_err(|_| failure::err_msg("invalid b64"))?;
|
||||||
|
let token_str = self.sealed_state.unseal(token_sealed)
|
||||||
|
.map_err(|_| failure::err_msg("failed to validate"))?;
|
||||||
let token: Token = serde_json::from_str(&token_str)
|
let token: Token = serde_json::from_str(&token_str)
|
||||||
.map_err(|_| "failed to deserialize")?;
|
.map_err(|_| failure::err_msg("failed to deserialize"))?;
|
||||||
|
|
||||||
let elapsed = current_time() - token.creation;
|
let elapsed = current_time() - token.creation;
|
||||||
if elapsed > self.validity {
|
if elapsed > self.validity {
|
||||||
Err("Token has expired!")?;
|
Err(failure::err_msg("Token has expired!"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(token.fpr)
|
Ok(token.fpr)
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
use rocket;
|
||||||
|
use rocket::State;
|
||||||
|
use rocket::request::Form;
|
||||||
|
|
||||||
|
use failure::Fallible as Result;
|
||||||
|
|
||||||
|
use web::{MyResponse,templates::General};
|
||||||
|
use database::{Database, Polymorphic};
|
||||||
|
use database::types::Email;
|
||||||
|
use mail;
|
||||||
|
use tokens;
|
||||||
|
|
||||||
|
mod templates {
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ManageKey {
|
||||||
|
// pub uid_unpublished: Option<String>,
|
||||||
|
pub uid_status: Vec<ManageKeyUidStatus>,
|
||||||
|
pub token: String,
|
||||||
|
pub commit: String,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ManageLinkSent {
|
||||||
|
pub address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ManageKeyUidStatus {
|
||||||
|
pub address: String,
|
||||||
|
pub published: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod forms {
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct ManageRequest {
|
||||||
|
pub search_term: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct ManageDelete {
|
||||||
|
pub token: String,
|
||||||
|
pub address: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/vks/manage")]
|
||||||
|
pub fn vks_manage() -> Result<MyResponse> {
|
||||||
|
Ok(MyResponse::ok("manage/manage", General::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/vks/manage/<token>")]
|
||||||
|
pub fn vks_manage_key(
|
||||||
|
db: State<Polymorphic>,
|
||||||
|
token: String,
|
||||||
|
token_service: rocket::State<tokens::Service>,
|
||||||
|
) -> MyResponse {
|
||||||
|
if let Ok(fingerprint) = token_service.check(&token) {
|
||||||
|
match db.lookup(&database::Query::ByFingerprint(fingerprint)) {
|
||||||
|
Ok(Some(tpk)) => {
|
||||||
|
let mut emails: Vec<Email> = tpk.userids()
|
||||||
|
.map(|u| u.userid().to_string().parse::<Email>())
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
emails.sort_unstable();
|
||||||
|
emails.dedup();
|
||||||
|
let uid_status = emails.into_iter().map(|email|
|
||||||
|
templates::ManageKeyUidStatus {
|
||||||
|
address: email.to_string(),
|
||||||
|
published: true,
|
||||||
|
}
|
||||||
|
).collect();
|
||||||
|
let context = templates::ManageKey {
|
||||||
|
uid_status,
|
||||||
|
token,
|
||||||
|
version: env!("VERGEN_SEMVER").to_string(),
|
||||||
|
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||||
|
};
|
||||||
|
MyResponse::ok("manage/manage_key", context)
|
||||||
|
},
|
||||||
|
Ok(None) => MyResponse::not_found(
|
||||||
|
Some("manage/manage"),
|
||||||
|
Some("This link is invalid or expired".to_owned())),
|
||||||
|
Err(e) => MyResponse::ise(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MyResponse::ok("manage/manage_expired", General::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/vks/manage", data="<request>")]
|
||||||
|
pub fn vks_manage_post(
|
||||||
|
db: State<Polymorphic>,
|
||||||
|
request: Form<forms::ManageRequest>,
|
||||||
|
token_service: rocket::State<tokens::Service>,
|
||||||
|
mail_service: Option<rocket::State<mail::Service>>,
|
||||||
|
) -> MyResponse {
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
let email = match request.search_term.parse::<Email>() {
|
||||||
|
Ok(email) => email,
|
||||||
|
Err(_) => return MyResponse::not_found(
|
||||||
|
Some("manage/manage"),
|
||||||
|
Some(format!("Malformed email address: {:?}", request.search_term)))
|
||||||
|
};
|
||||||
|
|
||||||
|
let tpk = match db.lookup(&database::Query::ByEmail(email.clone())) {
|
||||||
|
Ok(Some(tpk)) => tpk,
|
||||||
|
Ok(None) => return MyResponse::not_found(
|
||||||
|
Some("manage/manage"),
|
||||||
|
Some(format!("No key for address {:?}", request.search_term))),
|
||||||
|
Err(e) => return MyResponse::ise(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let fpr = tpk.fingerprint().try_into().unwrap();
|
||||||
|
let token = token_service.create(&fpr);
|
||||||
|
let token_uri = uri!(vks_manage_key: token).to_string();
|
||||||
|
if let Some(mail_service) = mail_service {
|
||||||
|
for binding in tpk.userids() {
|
||||||
|
let email_candidate = binding.userid().to_string().parse::<Email>();
|
||||||
|
if let Ok(email_candidate) = email_candidate {
|
||||||
|
if &email_candidate != &email {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Err(e) = mail_service.send_manage_token(
|
||||||
|
&[email_candidate], &token_uri) {
|
||||||
|
return MyResponse::ise(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ctx = templates::ManageLinkSent {
|
||||||
|
address: email.to_string(),
|
||||||
|
};
|
||||||
|
MyResponse::ok("manage/manage_link_sent", ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/vks/manage/unpublish", data="<request>")]
|
||||||
|
pub fn vks_manage_unpublish(
|
||||||
|
db: rocket::State<Polymorphic>,
|
||||||
|
token_service: rocket::State<tokens::Service>,
|
||||||
|
request: Form<forms::ManageDelete>,
|
||||||
|
) -> MyResponse {
|
||||||
|
match vks_manage_unpublish_or_fail(db, token_service, request) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(e) => MyResponse::ise(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vks_manage_unpublish_or_fail(
|
||||||
|
db: rocket::State<Polymorphic>,
|
||||||
|
token_service: rocket::State<tokens::Service>,
|
||||||
|
request: Form<forms::ManageDelete>,
|
||||||
|
) -> Result<MyResponse> {
|
||||||
|
let fpr = token_service.check(&request.token)?;
|
||||||
|
let email = request.address.parse::<Email>()?;
|
||||||
|
db.delete_userids_matching(&fpr, &email)?;
|
||||||
|
Ok(vks_manage_key(db, request.token.to_owned(), token_service))
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ use Result;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
mod hkp;
|
mod hkp;
|
||||||
|
mod manage;
|
||||||
|
|
||||||
use rocket::http::hyper::header::ContentDisposition;
|
use rocket::http::hyper::header::ContentDisposition;
|
||||||
|
|
||||||
|
@ -354,6 +355,11 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
||||||
// HKP
|
// HKP
|
||||||
hkp::pks_lookup,
|
hkp::pks_lookup,
|
||||||
hkp::pks_add,
|
hkp::pks_add,
|
||||||
|
// EManage
|
||||||
|
manage::vks_manage,
|
||||||
|
manage::vks_manage_key,
|
||||||
|
manage::vks_manage_post,
|
||||||
|
manage::vks_manage_unpublish,
|
||||||
];
|
];
|
||||||
|
|
||||||
use database::{Filesystem, Polymorphic};
|
use database::{Filesystem, Polymorphic};
|
||||||
|
@ -375,12 +381,16 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
||||||
// Mail service
|
// Mail service
|
||||||
let template_dir: PathBuf = rocket.config().get_str("template_dir")?.into();
|
let template_dir: PathBuf = rocket.config().get_str("template_dir")?.into();
|
||||||
let from = rocket.config().get_str("from")?.to_string();
|
let from = rocket.config().get_str("from")?.to_string();
|
||||||
let verify_html = template_dir.join("publish-email-html.hbs");
|
let verify_html = template_dir.join("email/publish-html.hbs");
|
||||||
let verify_txt = template_dir.join("publish-email-txt.hbs");
|
let verify_txt = template_dir.join("email/publish-txt.hbs");
|
||||||
|
let manage_html = template_dir.join("email/manage-html.hbs");
|
||||||
|
let manage_txt = template_dir.join("email/manage-txt.hbs");
|
||||||
|
|
||||||
let mut handlebars = Handlebars::new();
|
let mut handlebars = Handlebars::new();
|
||||||
handlebars.register_template_file("verify-html", verify_html)?;
|
handlebars.register_template_file("verify-html", verify_html)?;
|
||||||
handlebars.register_template_file("verify-txt", verify_txt)?;
|
handlebars.register_template_file("verify-txt", verify_txt)?;
|
||||||
|
handlebars.register_template_file("manage-html", manage_html)?;
|
||||||
|
handlebars.register_template_file("manage-txt", manage_txt)?;
|
||||||
|
|
||||||
let filemail_into = rocket.config().get_str("filemail_into")
|
let filemail_into = rocket.config().get_str("filemail_into")
|
||||||
.ok().map(|p| PathBuf::from(p));
|
.ok().map(|p| PathBuf::from(p));
|
||||||
|
|
Loading…
Reference in New Issue