i18n: localize mail templates

This commit is contained in:
Vincent Breitmoser 2019-08-28 20:33:24 +02:00
parent 77c90fc8fb
commit 77e9c13b5b
No known key found for this signature in database
GPG Key ID: 7BD18320DEADFA11
17 changed files with 183 additions and 101 deletions

21
Cargo.lock generated
View File

@ -191,11 +191,6 @@ name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.2"
@ -607,15 +602,6 @@ dependencies = [
"wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gettext"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gettext"
version = "0.4.0"
@ -655,14 +641,14 @@ version = "0.1.0"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gettext-macros 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hagrid-database 0.1.0",
"handlebars 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (git+https://github.com/lettre/lettre)",
"lettre_email 0.9.2 (git+https://github.com/lettre/lettre)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"multipart 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -672,6 +658,7 @@ dependencies = [
"rocket_contrib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_i18n 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_prometheus 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"runtime-fmt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sequoia-openpgp 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2428,7 +2415,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum buffered-reader 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "237cf351e1e6666907f4e2b59ee4a00083280445a0c6eb2261640615a3a33317"
"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708"
"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
"checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101"
"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff"
@ -2478,7 +2464,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
"checksum getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "fc344b02d3868feb131e8b5fe2b9b0a1cc42942679af493061fc13b853243872"
"checksum gettext 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4378b8e09fd51cfdb0d48f40929a5c358efeeb62feb458c7d6eab979fae231f4"
"checksum gettext 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ebb594e753d5997e4be036e5a8cf048ab9414352870fb45c779557bbc9ba971"
"checksum gettext-macros 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fbf88e9e6bba5ff64428ee2a32ea9872d94d4c87c42fb3f219bf87f63e4b9d51"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"

View File

@ -19,7 +19,6 @@ rocket = "0"
rocket_codegen = "0"
sequoia-openpgp = { version = "0.9", default-features = false }
multipart = "0"
log = "0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
@ -36,8 +35,10 @@ uuid = "0.7"
rocket_prometheus = "0.2"
lazy_static = "1.3.0"
rocket_i18n = "0.4"
gettext-macros = "0.5.1"
gettext = "0.3.0"
gettext-macros = "0.5"
gettext = "0.4"
runtime-fmt = "0.4"
glob = "0.3"
[dependencies.lettre]
version = "0.9"

View File

@ -17,6 +17,7 @@ tmp_dir = "state/tmp"
mail_rate_limit = 60
maintenance_file = "state/maintenance"
enable_prometheus = false
email_template_dir = "dist/email-templates"
[staging]
base-URI = "https://keys.openpgp.org"
@ -33,6 +34,7 @@ tmp_dir = "tmp"
mail_rate_limit = 60
maintenance_file = "maintenance"
enable_prometheus = false
email_template_dir = "email-templates"
[production]
base-URI = "https://keys.openpgp.org"
@ -50,3 +52,4 @@ tmp_dir = "tmp"
mail_rate_limit = 3600
maintenance_file = "maintenance"
enable_prometheus = false
email_template_dir = "email-templates"

View File

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use failure;
use handlebars::Handlebars;
@ -12,6 +12,8 @@ use crate::counters;
use crate::database::types::Email;
use crate::Result;
use rocket_i18n::I18n;
mod context {
#[derive(Serialize, Clone)]
pub struct Verification {
@ -53,36 +55,33 @@ enum Transport {
impl Service {
/// Sends mail via sendmail.
pub fn sendmail(from: String, base_uri: String, templates: Handlebars)
-> Result<Self> {
Self::new(from, base_uri, templates, Transport::Sendmail)
pub fn sendmail(from: String, base_uri: String, template_dir: PathBuf) -> Result<Self> {
Self::new(from, base_uri, template_dir, Transport::Sendmail)
}
/// Sends mail by storing it in the given directory.
pub fn filemail(from: String, base_uri: String, templates: Handlebars,
path: PathBuf)
-> Result<Self> {
Self::new(from, base_uri, templates, Transport::Filemail(path))
pub fn filemail(from: String, base_uri: String, template_dir: PathBuf, path: PathBuf) -> Result<Self> {
Self::new(from, base_uri, template_dir, Transport::Filemail(path))
}
fn new(from: String, base_uri: String, templates: Handlebars,
transport: Transport)
fn new(from: String, base_uri: String, template_dir: PathBuf, transport: Transport)
-> Result<Self> {
let templates = load_handlebars(template_dir)?;
let domain =
url::Url::parse(&base_uri)
?.host_str().ok_or_else(|| failure::err_msg("No host in base-URI"))
?.to_string();
Ok(Self {
from: from.parse().unwrap(),
domain: domain,
templates: templates,
transport: transport,
})
Ok(Self { from: from.parse().unwrap(), domain, templates, transport })
}
pub fn send_verification(&self, base_uri: &str, tpk_name: String, userid: &Email,
token: &str)
-> Result<()> {
pub fn send_verification(
&self,
i18n: &I18n,
base_uri: &str,
tpk_name: String,
userid: &Email,
token: &str
) -> Result<()> {
let ctx = context::Verification {
primary_fp: tpk_name,
uri: format!("{}/verify/{}", base_uri, token),
@ -95,14 +94,22 @@ impl Service {
self.send(
&vec![userid],
// i18n!(i18n.catalog, "Verify {} for your key on {}"; userid, self.domain),
&format!("Verify {} for your key on {}", userid, self.domain),
"verify",
i18n.lang,
ctx,
)
}
pub fn send_manage_token(&self, base_uri: &str, tpk_name: String, recipient: &Email,
link_path: &str) -> Result<()> {
pub fn send_manage_token(
&self,
i18n: &I18n,
base_uri: &str,
tpk_name: String,
recipient: &Email,
link_path: &str,
) -> Result<()> {
let ctx = context::Manage {
primary_fp: tpk_name,
uri: format!("{}{}", base_uri, link_path),
@ -114,15 +121,22 @@ impl Service {
self.send(
&[recipient],
// i18n!(i18n.catalog, "Manage your key on {}"; self.domain),
&format!("Manage your key on {}", self.domain),
"manage",
i18n.lang,
ctx,
)
}
pub fn send_welcome(&self, base_uri: &str, tpk_name: String, userid: &Email,
token: &str)
-> Result<()> {
pub fn send_welcome(
&self,
i18n: &I18n,
base_uri: &str,
tpk_name: String,
userid: &Email,
token: &str
) -> Result<()> {
let ctx = context::Welcome {
primary_fp: tpk_name,
uri: format!("{}/upload/{}", base_uri, token),
@ -134,43 +148,46 @@ impl Service {
self.send(
&vec![userid],
// i18n!(i18n.catalog, "Your key upload on {}"; self.domain),
&format!("Your key upload on {}", self.domain),
"welcome",
i18n.lang,
ctx,
)
}
fn send<T>(&self, to: &[&Email], subject: &str, template: &str, ctx: T)
-> Result<()>
where T: Serialize + Clone,
{
let tmpl_html = format!("{}-html", template);
let tmpl_txt = format!("{}-txt", template);
let (html, txt) = {
if let (Ok(inner_html), Ok(inner_txt)) = (
self.templates.render(&tmpl_html, &ctx),
self.templates.render(&tmpl_txt, &ctx),
) {
(Some(inner_html), Some(inner_txt))
} else {
(None, None)
}
};
fn render_template(&self, template: &str, locale: &str, ctx: impl Serialize + Clone) -> Result<(String, String)> {
let html = self.templates.render(&format!("{}/{}.htm", locale, template), &ctx)
.or_else(|_| self.templates.render(&format!("{}.htm", template), &ctx))
.map_err(|_| failure::err_msg("Email template failed to render"))?;
let txt = self.templates.render(&format!("{}/{}.txt", locale, template), &ctx)
.or_else(|_| self.templates.render(&format!("{}.txt", template), &ctx))
.map_err(|_| failure::err_msg("Email template failed to render"))?;
Ok((html, txt))
}
fn send(
&self,
to: &[&Email],
subject: &str,
template: &str,
locale: &str,
ctx: impl Serialize + Clone
) -> Result<()> {
let (html, txt) = self.render_template(template, locale, ctx)?;
if cfg!(debug_assertions) {
for recipient in to.iter() {
println!("To: {}", recipient.to_string());
}
println!("{}", txt.as_ref().unwrap().to_string());
println!("{}", &txt);
}
let email = EmailBuilder::new()
.from(self.from.clone())
.subject(subject)
.alternative(
html.ok_or(failure::err_msg("Email template failed to render"))?,
txt.ok_or(failure::err_msg("Email template failed to render"))?,
)
.alternative(html, txt)
.message_id(format!("<{}@{}>", Uuid::new_v4(), self.domain));
let email = to.iter().fold(email, |email, to| email.to(to.to_string()));
@ -191,3 +208,32 @@ impl Service {
Ok(())
}
}
fn load_handlebars(template_dir: PathBuf) -> Result<Handlebars> {
let mut handlebars = Handlebars::new();
let mut glob_path = template_dir.join("**").join("*");
glob_path.set_extension("hbs");
let glob_path = glob_path.to_str().expect("valid glob path string");
for path in glob::glob(glob_path).unwrap().flatten() {
let template_name = remove_extension(path.strip_prefix(&template_dir)?);
handlebars.register_template_file(&template_name.to_string_lossy(), &path)?;
}
Ok(handlebars)
}
fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let stem = match path.file_stem() {
Some(stem) => stem,
None => return path.to_path_buf()
};
match path.parent() {
Some(parent) => parent.join(stem),
None => PathBuf::from(stem)
}
}

View File

@ -26,10 +26,12 @@ extern crate lettre_email;
extern crate rocket_prometheus;
extern crate tempfile;
extern crate uuid;
extern crate glob;
extern crate gettext;
extern crate gettext_macros;
extern crate rocket_i18n;
extern crate runtime_fmt;
#[cfg(test)]
extern crate regex;
@ -37,14 +39,19 @@ extern crate regex;
extern crate ring;
extern crate hagrid_database as database;
use gettext_macros::init_i18n;
init_i18n!("hagrid", en, de);
mod mail;
mod anonymize_utils;
mod web;
mod tokens;
mod sealed_state;
mod rate_limiter;
mod dump;
mod counters;
mod web;
fn main() {
if let Err(e) = web::serve() {

View File

@ -5,6 +5,7 @@ use rocket::Outcome;
use rocket::http::{ContentType, Status};
use rocket::request::{self, Request, FromRequest};
use rocket::http::uri::Uri;
use rocket_i18n::I18n;
use crate::database::{Database, Query, KeyDatabase};
use crate::database::types::{Email, Fingerprint, KeyID};
@ -131,11 +132,12 @@ pub fn pks_add_form(
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
mail_service: rocket::State<mail::Service>,
i18n: I18n,
data: Data,
) -> MyResponse {
match vks_web::process_post_form(db, tokens_stateless, rate_limiter, data) {
Ok(UploadResponse::Ok { is_new_key, key_fpr, primary_uid, token, .. }) => {
let msg = if is_new_key && send_welcome_mail(&request_origin, &mail_service, key_fpr, primary_uid, token) {
let msg = if is_new_key && send_welcome_mail(&request_origin, &mail_service, &i18n, key_fpr, primary_uid, token) {
"Upload successful. This is a new key, a welcome mail has been sent!".to_owned()
} else {
format!("Upload successful. Note that identity information will only be published after verification! see {}/about/usage#gnupg-upload", request_origin.get_base_uri())
@ -153,13 +155,14 @@ pub fn pks_add_form(
fn send_welcome_mail(
request_origin: &RequestOrigin,
mail_service: &mail::Service,
i18n: &I18n,
fpr: String,
primary_uid: Option<Email>,
token: String,
) -> bool {
if let Some(primary_uid) = primary_uid {
mail_service.send_welcome(
request_origin.get_base_uri(), fpr, &primary_uid, &token).is_ok()
i18n, request_origin.get_base_uri(), fpr, &primary_uid, &token).is_ok()
} else {
false
}

View File

@ -1,6 +1,7 @@
use rocket;
use rocket::State;
use rocket::request::Form;
use rocket_i18n::I18n;
use failure::Fallible as Result;
@ -116,6 +117,7 @@ pub fn vks_manage_post(
request_origin: RequestOrigin,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
i18n: I18n,
request: Form<forms::ManageRequest>,
token_service: rocket::State<tokens::Service>,
) -> MyResponse {
@ -156,7 +158,7 @@ pub fn vks_manage_post(
let link_path = uri!(vks_manage_key: token).to_string();
let base_uri = request_origin.get_base_uri();
if let Err(e) = mail_service.send_manage_token(base_uri, fpr_text, &email, &link_path) {
if let Err(e) = mail_service.send_manage_token(&i18n, base_uri, fpr_text, &email, &link_path) {
return MyResponse::ise(e);
}

View File

@ -11,10 +11,9 @@ use rocket::response::status::Custom;
use rocket_prometheus::PrometheusMetrics;
use gettext_macros::{compile_i18n, include_i18n, init_i18n};
use gettext_macros::{compile_i18n, include_i18n};
use serde::Serialize;
use handlebars::Handlebars;
use std::path::PathBuf;
@ -337,7 +336,7 @@ pub fn serve() -> Result<()> {
Err(rocket_factory(rocket::ignite())?.launch().into())
}
init_i18n!("hagrid", en, de);
compile_i18n!();
fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
let routes = routes![
@ -420,8 +419,6 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
Ok(rocket)
}
compile_i18n!();
fn configure_prometheus(config: &Config) -> Option<PrometheusMetrics> {
if !config.get_bool("enable_prometheus").unwrap_or(false) {
return None;
@ -477,31 +474,18 @@ fn configure_stateless_token_service(config: &Config) -> Result<tokens::Service>
fn configure_mail_service(config: &Config) -> Result<mail::Service> {
// Mail service
let template_dir: PathBuf = config.get_str("template_dir")?.into();
let email_template_dir: PathBuf = config.get_str("email_template_dir")?.into();
let base_uri = config.get_str("base-URI")?.to_string();
let from = config.get_str("from")?.to_string();
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 welcome_html = template_dir.join("email/welcome-html.hbs");
let welcome_txt = template_dir.join("email/welcome-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)?;
handlebars.register_template_file("welcome-html", welcome_html)?;
handlebars.register_template_file("welcome-txt", welcome_txt)?;
let filemail_into = config.get_str("filemail_into")
.ok().map(|p| PathBuf::from(p));
if let Some(path) = filemail_into {
mail::Service::filemail(from, base_uri, handlebars, path)
mail::Service::filemail(from, base_uri, email_template_dir, path)
} else {
mail::Service::sendmail(from, base_uri, handlebars)
mail::Service::sendmail(from, base_uri, email_template_dir)
}
}
@ -574,6 +558,9 @@ pub mod tests {
.extra("template_dir",
::std::env::current_dir().unwrap().join("dist/templates")
.to_str().unwrap())
.extra("email_template_dir",
::std::env::current_dir().unwrap().join("dist/email-templates")
.to_str().unwrap())
.extra("assets_dir",
::std::env::current_dir().unwrap().join("dist/assets")
.to_str().unwrap())
@ -714,7 +701,7 @@ pub mod tests {
check_hr_responses_by_fingerprint(&client, &tpk, 0);
// Check the verification link
check_verify_link(&client, &token, "foo@invalid.example.com");
check_verify_link(&client, &token, "foo@invalid.example.com", "");
// Now check for the verification mail.
check_mails_and_verify_email(&client, filemail_into.as_path());
@ -744,6 +731,25 @@ pub mod tests {
assert_consistency(client.rocket());
}
#[test]
fn upload_verify_lang() {
let (tmpdir, client) = client().unwrap();
let filemail_into = tmpdir.path().join("filemail");
// Generate a key and upload it.
let (tpk, _) = TPKBuilder::autocrypt(
None, Some("foo@invalid.example.com"))
.generate().unwrap();
let mut tpk_serialized = Vec::new();
tpk.serialize(&mut tpk_serialized).unwrap();
let token = vks_publish_submit_get_token(&client, &tpk_serialized);
check_verify_link(&client, &token, "foo@invalid.example.com", "de, en");
let mail_content = pop_mail(&filemail_into).unwrap().unwrap();
assert!(mail_content.contains("dies ist eine automatische Nachricht"))
}
#[test]
fn upload_two() {
let (_tmpdir, config) = configuration().unwrap();
@ -818,7 +824,7 @@ pub mod tests {
check_hr_responses_by_fingerprint(&client, &tpk_2, 0);
// Check the verification link
check_verify_link(&client, &token_1, "foo@invalid.example.com");
check_verify_link(&client, &token_1, "foo@invalid.example.com", "");
check_verify_link_json(&client, &token_2, "bar@invalid.example.com");
// Now check for the verification mails.
@ -1075,7 +1081,7 @@ pub mod tests {
&tpk, nr_uids);
}
fn check_verify_link(client: &Client, token: &str, address: &str) {
fn check_verify_link(client: &Client, token: &str, address: &str, lang: &'static str) {
let encoded = ::url::form_urlencoded::Serializer::new(String::new())
.append_pair("token", token)
.append_pair("address", address)
@ -1083,6 +1089,7 @@ pub mod tests {
let response = client.post("/upload/request-verify")
.header(ContentType::Form)
.header(Header::new("Accept-Language", lang))
.body(encoded.as_bytes())
.dispatch();
assert_eq!(response.status(), Status::Ok);

View File

@ -8,6 +8,8 @@ use crate::tokens::{self, StatelessSerializable};
use crate::rate_limiter::RateLimiter;
use crate::web::RequestOrigin;
use rocket_i18n::I18n;
use sequoia_openpgp::TPK;
use std::io::Read;
@ -192,6 +194,7 @@ pub fn request_verify(
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
i18n: I18n,
token: String,
addresses: Vec<String>,
) -> response::UploadResponse {
@ -218,7 +221,7 @@ pub fn request_verify(
for email in emails_requested {
let rate_limit_ok = rate_limiter.action_perform(format!("verify-{}", &email));
if rate_limit_ok {
if send_verify_email(&request_origin, &mail_service, &token_stateful, &verify_state.fpr, &email).is_err() {
if send_verify_email(&request_origin, &mail_service, &token_stateful, &i18n, &verify_state.fpr, &email).is_err() {
return UploadResponse::err(&format!("error sending email to {}", &email));
}
}
@ -241,6 +244,7 @@ fn send_verify_email(
request_origin: &RequestOrigin,
mail_service: &mail::Service,
token_stateful: &StatefulTokens,
i18n: &I18n,
fpr: &Fingerprint,
email: &Email,
) -> Result<()> {
@ -249,6 +253,7 @@ fn send_verify_email(
let token_verify = token_stateful.new_token("verify", token_str.as_bytes())?;
mail_service.send_verification(
i18n,
request_origin.get_base_uri(),
fpr.to_string(),
&email,

View File

@ -2,6 +2,8 @@ use rocket_contrib::json::{Json,JsonValue,JsonError};
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::{ContentType,Status};
use rocket::State;
use rocket_i18n::{I18n, Translations};
use std::io::Cursor;
use crate::database::{KeyDatabase, StatefulTokens, Query};
@ -23,6 +25,7 @@ pub mod json {
pub struct VerifyRequest {
pub token: String,
pub addresses: Vec<String>,
pub locale: Option<Vec<String>>,
}
#[derive(Deserialize)]
@ -93,9 +96,24 @@ pub fn upload_fallback(
JsonErrorResponse(Status::BadRequest, error_msg)
}
fn get_locale(
langs: State<Translations>,
locales: Vec<String>,
) -> I18n {
locales
.iter()
.flat_map(|lang| lang.split(|c| c == '-' || c == ';' || c == '_').next())
.flat_map(|lang| langs.iter().find(|(trans, _)| trans == &lang))
.next()
.or_else(|| langs.iter().find(|(trans, _)| trans == &"en"))
.map(|(lang, catalog)| I18n { catalog: catalog.clone(), lang })
.expect("Expected to have an english translation!")
}
#[post("/vks/v1/request-verify", format = "json", data="<data>")]
pub fn request_verify_json(
db: rocket::State<KeyDatabase>,
langs: State<Translations>,
request_origin: RequestOrigin,
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
@ -104,10 +122,11 @@ pub fn request_verify_json(
data: Result<Json<json::VerifyRequest>, JsonError>,
) -> JsonResult {
let data = json_or_error(data)?;
let json::VerifyRequest { token, addresses } = data.into_inner();
let json::VerifyRequest { token, addresses, locale } = data.into_inner();
let i18n = get_locale(langs, locale.unwrap_or_default());
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, token, addresses);
rate_limiter, i18n, token, addresses);
upload_ok_json(result)
}

View File

@ -8,6 +8,7 @@ use multipart::server::Multipart;
use rocket::http::ContentType;
use rocket::request::Form;
use rocket::Data;
use rocket_i18n::I18n;
use crate::database::{KeyDatabase, StatefulTokens, Query, Database};
use crate::mail;
@ -310,11 +311,12 @@ pub fn quick_upload_proceed(
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
i18n: I18n,
token: String,
) -> MyResponse {
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, token, vec!());
rate_limiter, i18n, token, vec!());
MyResponse::upload_response(result)
}
@ -416,12 +418,13 @@ pub fn request_verify_form(
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
i18n: I18n,
request: Form<forms::VerifyRequest>,
) -> MyResponse {
let forms::VerifyRequest { token, address } = request.into_inner();
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, token, vec!(address));
rate_limiter, i18n, token, vec!(address));
MyResponse::upload_response(result)
}
@ -433,12 +436,13 @@ pub fn request_verify_form_data(
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
i18n: I18n,
request: Form<forms::VerifyRequest>,
) -> MyResponse {
let forms::VerifyRequest { token, address } = request.into_inner();
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, token, vec!(address));
rate_limiter, i18n, token, vec!(address));
MyResponse::upload_response(result)
}