(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
|
||||
///
|
||||
/// 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);
|
||||
|
||||
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 }}
|
||||
{{> search-form}}
|
||||
<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 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 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>
|
||||
|
||||
<script>
|
||||
|
|
18
src/mail.rs
18
src/mail.rs
|
@ -23,7 +23,7 @@ mod context {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Deletion {
|
||||
pub struct Manage {
|
||||
pub uri: String,
|
||||
pub base_uri: 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)
|
||||
-> Result<()>
|
||||
where T: Serialize + Clone,
|
||||
|
|
|
@ -2,6 +2,7 @@ use sealed_state::SealedState;
|
|||
|
||||
use database::types::{Fingerprint};
|
||||
use serde_json;
|
||||
use Result;
|
||||
|
||||
const REVISION: u8 = 1;
|
||||
|
||||
|
@ -36,15 +37,17 @@ impl Service {
|
|||
base64::encode_config(&token_sealed, base64::URL_SAFE_NO_PAD)
|
||||
}
|
||||
|
||||
pub fn check(&self, token_encoded: &str) -> Result<Fingerprint, String> {
|
||||
let token_sealed = base64::decode_config(&token_encoded, base64::URL_SAFE_NO_PAD).map_err(|_| "invalid b64")?;
|
||||
let token_str = self.sealed_state.unseal(token_sealed)?;
|
||||
pub fn check(&self, token_encoded: &str) -> Result<Fingerprint> {
|
||||
let token_sealed = base64::decode_config(&token_encoded, base64::URL_SAFE_NO_PAD)
|
||||
.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)
|
||||
.map_err(|_| "failed to deserialize")?;
|
||||
.map_err(|_| failure::err_msg("failed to deserialize"))?;
|
||||
|
||||
let elapsed = current_time() - token.creation;
|
||||
if elapsed > self.validity {
|
||||
Err("Token has expired!")?;
|
||||
Err(failure::err_msg("Token has expired!"))?;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
mod hkp;
|
||||
mod manage;
|
||||
|
||||
use rocket::http::hyper::header::ContentDisposition;
|
||||
|
||||
|
@ -354,6 +355,11 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
|||
// HKP
|
||||
hkp::pks_lookup,
|
||||
hkp::pks_add,
|
||||
// EManage
|
||||
manage::vks_manage,
|
||||
manage::vks_manage_key,
|
||||
manage::vks_manage_post,
|
||||
manage::vks_manage_unpublish,
|
||||
];
|
||||
|
||||
use database::{Filesystem, Polymorphic};
|
||||
|
@ -375,12 +381,16 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
|||
// Mail service
|
||||
let template_dir: PathBuf = rocket.config().get_str("template_dir")?.into();
|
||||
let from = rocket.config().get_str("from")?.to_string();
|
||||
let verify_html = template_dir.join("publish-email-html.hbs");
|
||||
let verify_txt = template_dir.join("publish-email-txt.hbs");
|
||||
let verify_html = template_dir.join("email/publish-html.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();
|
||||
handlebars.register_template_file("verify-html", verify_html)?;
|
||||
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")
|
||||
.ok().map(|p| PathBuf::from(p));
|
||||
|
|
Loading…
Reference in New Issue