mirror of
https://gitlab.com/keys.openpgp.org/hagrid.git
synced 2025-10-07 00:53:37 +02:00
Compare commits
99 Commits
faq-mta-st
...
faq-partia
Author | SHA1 | Date | |
---|---|---|---|
|
facd64f236 | ||
|
351652d845 | ||
|
d15f0926ed | ||
|
230f69bc6f | ||
|
dae530d090 | ||
|
70ab7850d6 | ||
|
3a9965ccaa | ||
|
ec9e5f4d87 | ||
|
6b311dfa8b | ||
|
3cf2f08784 | ||
|
21a33d81f0 | ||
|
956afbf47a | ||
|
54382554d5 | ||
|
48e69e5899 | ||
|
7ff7129b31 | ||
|
a8d95fae83 | ||
|
ce846230aa | ||
|
9309d1c700 | ||
|
2127b3723a | ||
|
9f685d4b89 | ||
|
e0fea8013b | ||
|
10b177be8e | ||
|
555c895065 | ||
|
9f90a527ad | ||
|
9c0e7d6ffe | ||
|
2556073a63 | ||
|
130a13842d | ||
|
609da73320 | ||
|
b067c42f02 | ||
|
c624f2a5cf | ||
|
1a319fc2b9 | ||
|
8cf7adbb6c | ||
|
435f8d4743 | ||
|
9e62a001ab | ||
|
58aae5f8f8 | ||
|
7a8574c133 | ||
|
4934d15165 | ||
|
f38f5ced40 | ||
|
0f598323c8 | ||
|
ccb3a9fcbc | ||
|
c57c00b390 | ||
|
b91750372f | ||
|
178e6544a5 | ||
|
041385ae4e | ||
|
ef5a664827 | ||
|
07e793b023 | ||
|
ff66332ea7 | ||
|
695e4af7e9 | ||
|
c50b91e6cf | ||
|
0225e63b54 | ||
|
5704ac4434 | ||
|
cdc1afb6d1 | ||
|
6236d3bf40 | ||
|
069d332123 | ||
|
3355c7e5d4 | ||
|
1f2c4047d6 | ||
|
9152e2d3dd | ||
|
82f5450eb5 | ||
|
471e521484 | ||
|
c36161650b | ||
|
0fced131c5 | ||
|
331ef566ef | ||
|
77e9c13b5b | ||
|
77c90fc8fb | ||
|
6e0ab047ee | ||
|
ecdf1001f4 | ||
|
02321777b9 | ||
|
6df212f087 | ||
|
6b7cbbe1c1 | ||
|
ce7356cc72 | ||
|
52e863281d | ||
8805cb7790
|
|||
|
2c48d151b1 | ||
|
c180f3a905 | ||
|
6126436bca | ||
|
41ea7111d9 | ||
|
2eaec0b7dd | ||
|
2440d37485 | ||
|
eec7765b54 | ||
|
88aff3d5c4 | ||
|
a9a6c51fac | ||
|
8a74b74e32 | ||
|
dc4fc6b33f | ||
|
9a71103fe7 | ||
|
f38a530ae9 | ||
|
f64dcdb72a | ||
|
a8d69c0cbf | ||
|
8e079eb9fd | ||
|
389b85b43a | ||
|
45e667ca55 | ||
|
4dcdff15c2 | ||
|
9a551dc16e | ||
|
a69695dc2c | ||
|
5248b1d0b9 | ||
|
3b596d4955 | ||
|
dd6c69f11d | ||
|
9f4b82a9b1 | ||
|
d628acdf93 | ||
|
71d38ae865 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@
|
||||
**/.*.swp
|
||||
**/.*.swo
|
||||
target
|
||||
*.po~
|
||||
/dist/templates/localized
|
||||
|
@@ -10,5 +10,6 @@ build:binary:
|
||||
- 'echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list.d/testing-nettle.list'
|
||||
- 'echo "Package: *\nPin: release a=testing\nPin-Priority: 150" > /etc/apt/preferences.d/limit_testing'
|
||||
- apt update -qy
|
||||
- apt install -qy libclang-dev build-essential pkg-config clang nettle-dev/testing
|
||||
- apt install -qy libclang-dev build-essential pkg-config clang nettle-dev/testing gettext zsh
|
||||
- ./make-translated-templates
|
||||
- RUST_BACKTRACE=full cargo test --all
|
||||
|
59
.tx/config
Normal file
59
.tx/config
Normal file
@@ -0,0 +1,59 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[hagrid.hagrid]
|
||||
minimum_perc = 100
|
||||
source_file = po/hagrid/hagrid.pot
|
||||
source_lang = en
|
||||
file_filter = po/hagrid/<lang>.po
|
||||
trans.zh-Hans = po/hagrid/zh_Hans.po
|
||||
type = PO
|
||||
|
||||
[hagrid.about-about]
|
||||
minimum_perc = 100
|
||||
source_file = templates-untranslated/about/about.html.hbs
|
||||
file_filter = templates-translated/<lang>/about/about.html.hbs
|
||||
trans.zh-Hans = templates-translated/zh_Hans/about/about.html.hbs
|
||||
source_lang = en
|
||||
type = HTML
|
||||
|
||||
[hagrid.about-faq]
|
||||
minimum_perc = 100
|
||||
source_file = templates-untranslated/about/faq.html.hbs
|
||||
file_filter = templates-translated/<lang>/about/faq.html.hbs
|
||||
trans.zh-Hans = templates-translated/zh_Hans/about/faq.html.hbs
|
||||
source_lang = en
|
||||
type = HTML
|
||||
|
||||
[hagrid.about-news]
|
||||
minimum_perc = 100
|
||||
source_file = templates-untranslated/about/news.html.hbs
|
||||
file_filter = templates-translated/<lang>/about/news.html.hbs
|
||||
trans.zh-Hans = templates-translated/zh_Hans/about/news.html.hbs
|
||||
source_lang = en
|
||||
type = HTML
|
||||
|
||||
[hagrid.about-privacy]
|
||||
minimum_perc = 100
|
||||
source_file = templates-untranslated/about/privacy.html.hbs
|
||||
file_filter = templates-translated/<lang>/about/privacy.html.hbs
|
||||
trans.zh-Hans = templates-translated/zh_Hans/about/privacy.html.hbs
|
||||
source_lang = en
|
||||
type = HTML
|
||||
|
||||
[hagrid.about-stats]
|
||||
minimum_perc = 100
|
||||
source_file = templates-untranslated/about/stats.html.hbs
|
||||
file_filter = templates-translated/<lang>/about/stats.html.hbs
|
||||
trans.zh-Hans = templates-translated/zh_Hans/about/stats.html.hbs
|
||||
source_lang = en
|
||||
type = HTML
|
||||
|
||||
[hagrid.about-usage]
|
||||
minimum_perc = 100
|
||||
source_file = templates-untranslated/about/usage.html.hbs
|
||||
file_filter = templates-translated/<lang>/about/usage.html.hbs
|
||||
trans.zh-Hans = templates-translated/zh_Hans/about/usage.html.hbs
|
||||
source_lang = en
|
||||
type = HTML
|
||||
|
1263
Cargo.lock
generated
1263
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "hagrid"
|
||||
version = "0.1.0"
|
||||
authors = ["Kai Michaelis <kai@sequoia-pgp.org>"]
|
||||
authors = ["Vincent Breitmoser <look@my.amazin.horse>", "Kai Michaelis <kai@sequoia-pgp.org>"]
|
||||
build = "build.rs"
|
||||
default-run = "hagrid"
|
||||
edition = "2018"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
@@ -16,9 +17,8 @@ hagrid-database = { path = "database" }
|
||||
failure = "0.1.5"
|
||||
rocket = "0"
|
||||
rocket_codegen = "0"
|
||||
sequoia-openpgp = { version = "0.8", default-features = false }
|
||||
sequoia-openpgp = { version = "0.9", default-features = false }
|
||||
multipart = "0"
|
||||
log = "0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
@@ -32,6 +32,14 @@ num_cpus = "1.0"
|
||||
ring = "0.13"
|
||||
base64 = "0.10"
|
||||
uuid = "0.7"
|
||||
rocket_prometheus = "0.2"
|
||||
lazy_static = "1.3.0"
|
||||
rocket_i18n = "0.4"
|
||||
gettext-macros = "0.5"
|
||||
runtime-fmt = "0.4"
|
||||
gettext = "0.4"
|
||||
glob = "0.3"
|
||||
rfc2047 = "0.1"
|
||||
|
||||
[dependencies.lettre]
|
||||
version = "0.9"
|
||||
|
@@ -4,7 +4,7 @@ Hagrid
|
||||
Hagrid is a verifying OpenPGP key server.
|
||||
|
||||
You can find general instructions and an API documentation at the running
|
||||
instance at [https://keys.openpgp.org](keys.openpgp.org).
|
||||
instance at [https://keys.openpgp.org](https://keys.openpgp.org).
|
||||
|
||||
License
|
||||
-------
|
||||
|
@@ -16,6 +16,8 @@ token_dir = "state/tokens"
|
||||
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"
|
||||
@@ -31,6 +33,8 @@ token_dir = "tokens"
|
||||
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"
|
||||
@@ -47,3 +51,5 @@ token_dir = "tokens"
|
||||
tmp_dir = "tmp"
|
||||
mail_rate_limit = 3600
|
||||
maintenance_file = "maintenance"
|
||||
enable_prometheus = false
|
||||
email_template_dir = "email-templates"
|
||||
|
@@ -5,7 +5,7 @@ authors = ["Kai Michaelis <kai@sequoia-pgp.org>"]
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1.5"
|
||||
sequoia-openpgp = { version = "0.8", default-features = false }
|
||||
sequoia-openpgp = { version = "0.9", default-features = false }
|
||||
multipart = "0"
|
||||
log = "0"
|
||||
rand = "0.6"
|
||||
@@ -21,6 +21,7 @@ pathdiff = "0.1"
|
||||
idna = "0.1"
|
||||
fs2 = "0.4"
|
||||
walkdir = "2.2"
|
||||
chrono = "0.4"
|
||||
|
||||
[lib]
|
||||
name = "hagrid_database"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::{create_dir_all, read_link, remove_file, rename, set_permissions, Permissions};
|
||||
use std::fs::{OpenOptions, File, create_dir_all, read_link, remove_file, rename, set_permissions, Permissions};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
@@ -8,6 +8,7 @@ use std::os::unix::fs::PermissionsExt;
|
||||
use tempfile;
|
||||
use url;
|
||||
use pathdiff::diff_paths;
|
||||
use std::time::SystemTime;
|
||||
|
||||
//use sequoia_openpgp::armor::{Writer, Kind};
|
||||
|
||||
@@ -28,6 +29,7 @@ pub struct Filesystem {
|
||||
keys_dir_full: PathBuf,
|
||||
keys_dir_quarantined: PathBuf,
|
||||
keys_dir_published: PathBuf,
|
||||
keys_dir_log: PathBuf,
|
||||
|
||||
links_dir_by_fingerprint: PathBuf,
|
||||
links_dir_by_keyid: PathBuf,
|
||||
@@ -78,10 +80,12 @@ impl Filesystem {
|
||||
let keys_external_dir: PathBuf = keys_external_dir.into();
|
||||
let keys_dir_full = keys_internal_dir.join("full");
|
||||
let keys_dir_quarantined = keys_internal_dir.join("quarantined");
|
||||
let keys_dir_log = keys_internal_dir.join("log");
|
||||
let keys_dir_published = keys_external_dir.join("pub");
|
||||
create_dir_all(&keys_dir_full)?;
|
||||
create_dir_all(&keys_dir_quarantined)?;
|
||||
create_dir_all(&keys_dir_published)?;
|
||||
create_dir_all(&keys_dir_log)?;
|
||||
|
||||
let links_dir = keys_external_dir.join("links");
|
||||
let links_dir_by_keyid = links_dir.join("by-keyid");
|
||||
@@ -103,6 +107,7 @@ impl Filesystem {
|
||||
keys_dir_full,
|
||||
keys_dir_published,
|
||||
keys_dir_quarantined,
|
||||
keys_dir_log,
|
||||
|
||||
links_dir_by_keyid,
|
||||
links_dir_by_fingerprint,
|
||||
@@ -199,6 +204,14 @@ impl Filesystem {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_logfile(&self, file_name: &str) -> Result<File> {
|
||||
let file_path = self.keys_dir_log.join(file_name);
|
||||
Ok(OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(file_path)?)
|
||||
}
|
||||
|
||||
fn perform_checks(
|
||||
&self,
|
||||
checks_dir: &Path,
|
||||
@@ -278,6 +291,19 @@ impl Database for Filesystem {
|
||||
Ok(tempfile)
|
||||
}
|
||||
|
||||
fn write_log_append(&self, filename: &str, fpr_primary: &Fingerprint) -> Result<()> {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let fingerprint_line = format!("{:010} {}\n", timestamp, fpr_primary.to_string());
|
||||
|
||||
self.open_logfile(filename)?
|
||||
.write_all(fingerprint_line.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_tmp_to_full(&self, file: NamedTempFile, fpr: &Fingerprint) -> Result<()> {
|
||||
if self.dry_run {
|
||||
return Ok(());
|
||||
@@ -478,7 +504,6 @@ impl Database for Filesystem {
|
||||
/// Note that this operation may take a long time, and is
|
||||
/// generally only useful for testing.
|
||||
fn check_consistency(&self) -> Result<()> {
|
||||
use std::collections::HashMap;
|
||||
use failure::format_err;
|
||||
|
||||
// A cache of all TPKs, for quick lookups.
|
||||
@@ -629,10 +654,17 @@ mod tests {
|
||||
let _ = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
fn open_db() -> (TempDir, Filesystem, PathBuf) {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
let log_path = db.keys_dir_log.join(db.get_current_log_filename());
|
||||
|
||||
(tmpdir, db, log_path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let (_tmp_dir, db, _log_path) = open_db();
|
||||
let k1 = TPKBuilder::new().add_userid("a@invalid.example.org")
|
||||
.generate().unwrap().0;
|
||||
let k2 = TPKBuilder::new().add_userid("b@invalid.example.org")
|
||||
@@ -650,125 +682,99 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn uid_verification() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_uid_verification(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_uid_verification(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid_deletion() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_uid_deletion(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_uid_deletion(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subkey_lookup() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_subkey_lookup(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_subkey_lookup(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kid_lookup() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_kid_lookup(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_kid_lookup(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upload_revoked_tpk() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
test::test_upload_revoked_tpk(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_upload_revoked_tpk(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid_revocation() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_uid_revocation(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_uid_revocation(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regenerate() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_regenerate(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_regenerate(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_reupload() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_reupload(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_reupload(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid_replacement() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_uid_replacement(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_uid_replacement(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uid_unlinking() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
test::test_unlink_uid(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_unlink_uid(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_email_1() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_same_email_1(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_same_email_1(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_email_2() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_same_email_2(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_same_email_2(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_selfsig() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_no_selfsig(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_no_selfsig(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_uids() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut db = Filesystem::new_from_base(tmpdir.path()).unwrap();
|
||||
|
||||
test::test_bad_uids(&mut db);
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::test_bad_uids(&mut db, &log_path);
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
#![feature(proc_macro_hygiene, plugin, decl_macro)]
|
||||
#![recursion_limit = "1024"]
|
||||
#![feature(try_from)]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::prelude::Utc;
|
||||
|
||||
extern crate failure;
|
||||
use failure::Error;
|
||||
use failure::Fallible as Result;
|
||||
@@ -21,6 +22,7 @@ extern crate time;
|
||||
extern crate url;
|
||||
extern crate hex;
|
||||
extern crate walkdir;
|
||||
extern crate chrono;
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
@@ -63,14 +65,15 @@ impl FromStr for Query {
|
||||
fn from_str(term: &str) -> Result<Self> {
|
||||
use self::Query::*;
|
||||
|
||||
if term.starts_with("0x") && term.len() < 16 && !term.contains('@') {
|
||||
let looks_like_short_key_id = !term.contains('@') &&
|
||||
(term.starts_with("0x") && term.len() < 16 || term.len() == 8);
|
||||
if looks_like_short_key_id {
|
||||
return Err(failure::err_msg(
|
||||
"Search by Short Key ID is not supported, sorry!"));
|
||||
}
|
||||
let term_nospace = term.replace(" ", "");
|
||||
if let Ok(fp) = Fingerprint::from_str(&term_nospace) {
|
||||
if let Ok(fp) = Fingerprint::from_str(term) {
|
||||
Ok(ByFingerprint(fp))
|
||||
} else if let Ok(keyid) = KeyID::from_str(&term_nospace) {
|
||||
} else if let Ok(keyid) = KeyID::from_str(term) {
|
||||
Ok(ByKeyID(keyid))
|
||||
} else if let Ok(email) = Email::from_str(term) {
|
||||
Ok(ByEmail(email))
|
||||
@@ -216,9 +219,11 @@ pub trait Database: Sync + Send {
|
||||
return Err(failure::err_msg("Not a well-formed key!"));
|
||||
}
|
||||
|
||||
let published_uids: Vec<UserID> = self
|
||||
let published_tpk_old = self
|
||||
.by_fpr(&fpr_primary)
|
||||
.and_then(|bytes| TPK::from_bytes(bytes.as_ref()).ok())
|
||||
.and_then(|bytes| TPK::from_bytes(bytes.as_ref()).ok());
|
||||
let published_uids: Vec<UserID> = published_tpk_old
|
||||
.as_ref()
|
||||
.map(|tpk| tpk.userids()
|
||||
.map(|binding| binding.userid().clone())
|
||||
.collect()
|
||||
@@ -310,6 +315,13 @@ pub trait Database: Sync + Send {
|
||||
self.move_tmp_to_full(full_tpk_tmp, &fpr_primary)?;
|
||||
self.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
|
||||
|
||||
let published_tpk_changed = published_tpk_old
|
||||
.map(|tpk| tpk != published_tpk_clean)
|
||||
.unwrap_or(true);
|
||||
if published_tpk_changed {
|
||||
self.update_write_log(&fpr_primary);
|
||||
}
|
||||
|
||||
for fpr in fpr_not_linked {
|
||||
if let Err(e) = self.link_fpr(&fpr, &fpr_primary) {
|
||||
info!("Error ensuring symlink! {} {} {:?}",
|
||||
@@ -331,6 +343,18 @@ pub trait Database: Sync + Send {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_write_log(&self, fpr_primary: &Fingerprint) {
|
||||
let log_name = self.get_current_log_filename();
|
||||
println!("{}", log_name);
|
||||
if let Err(e) = self.write_log_append(&log_name, fpr_primary) {
|
||||
error!("Error writing to log! {} {} {}", &log_name, &fpr_primary, e);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_log_filename(&self) -> String {
|
||||
Utc::now().format("%Y-%m-%d").to_string()
|
||||
}
|
||||
|
||||
fn get_tpk_status(&self, fpr_primary: &Fingerprint, known_addresses: &[Email]) -> Result<TpkStatus> {
|
||||
let tpk_full = self.by_fpr_full(&fpr_primary)
|
||||
.ok_or_else(|| failure::err_msg("Key not in database!"))
|
||||
@@ -435,6 +459,7 @@ pub trait Database: Sync + Send {
|
||||
let published_tpk_tmp = self.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
|
||||
|
||||
self.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
|
||||
self.update_write_log(&fpr_primary);
|
||||
|
||||
if let Err(e) = self.link_email(&email_new, &fpr_primary) {
|
||||
info!("Error ensuring email symlink! {} -> {} {:?}",
|
||||
@@ -518,6 +543,7 @@ pub trait Database: Sync + Send {
|
||||
let published_tpk_tmp = self.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
|
||||
|
||||
self.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
|
||||
self.update_write_log(&fpr_primary);
|
||||
|
||||
for unpublished_email in unpublished_emails {
|
||||
if let Err(e) = self.unlink_email(&unpublished_email, &fpr_primary) {
|
||||
@@ -602,6 +628,7 @@ pub trait Database: Sync + Send {
|
||||
fn move_tmp_to_full(&self, content: NamedTempFile, fpr: &Fingerprint) -> Result<()>;
|
||||
fn move_tmp_to_published(&self, content: NamedTempFile, fpr: &Fingerprint) -> Result<()>;
|
||||
fn write_to_quarantine(&self, fpr: &Fingerprint, content: &[u8]) -> Result<()>;
|
||||
fn write_log_append(&self, filename: &str, fpr_primary: &Fingerprint) -> Result<()>;
|
||||
|
||||
fn check_consistency(&self) -> Result<()>;
|
||||
}
|
||||
|
@@ -98,6 +98,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flock_nonexistent() {
|
||||
FlockMutexGuard::lock("nonexistent").is_err();
|
||||
assert!(FlockMutexGuard::lock("nonexistent").is_err());
|
||||
}
|
||||
}
|
||||
|
@@ -19,18 +19,20 @@ use std::str::FromStr;
|
||||
|
||||
use Database;
|
||||
use Query;
|
||||
use openpgp::tpk::{TPKBuilder, UserIDBinding};
|
||||
use openpgp::tpk::TPKBuilder;
|
||||
use openpgp::{
|
||||
constants::ReasonForRevocation, constants::SignatureType, packet::UserID,
|
||||
parse::Parse, Packet, PacketPile, RevocationStatus, TPK,
|
||||
packet::KeyFlags
|
||||
};
|
||||
use types::{Email, Fingerprint, KeyID};
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
|
||||
use TpkStatus;
|
||||
use EmailAddressStatus;
|
||||
|
||||
pub fn test_uid_verification<D: Database>(db: &mut D) {
|
||||
pub fn test_uid_verification(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
let str_uid2 = "Test B <test_b@example.com>";
|
||||
let tpk = TPKBuilder::new()
|
||||
@@ -43,10 +45,11 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
|
||||
let uid2 = UserID::from(str_uid2);
|
||||
let email1 = Email::from_str(str_uid1).unwrap();
|
||||
let email2 = Email::from_str(str_uid2).unwrap();
|
||||
let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
|
||||
// upload key
|
||||
let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
check_log_entry(log_path, &fpr);
|
||||
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
@@ -153,6 +156,7 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
|
||||
}
|
||||
|
||||
let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -262,7 +266,7 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
|
||||
}*/
|
||||
}
|
||||
|
||||
pub fn test_regenerate<D: Database>(db: &mut D) {
|
||||
pub fn test_regenerate(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
let tpk = TPKBuilder::new()
|
||||
.add_userid(str_uid1)
|
||||
@@ -284,6 +288,7 @@ pub fn test_regenerate<D: Database>(db: &mut D) {
|
||||
|
||||
// upload key
|
||||
db.merge(tpk).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
|
||||
db.regenerate_links(&fpr).unwrap();
|
||||
assert!(db.by_email(&email1).is_none());
|
||||
@@ -309,7 +314,7 @@ pub fn test_regenerate<D: Database>(db: &mut D) {
|
||||
assert!(db.check_consistency().is_ok());
|
||||
}
|
||||
|
||||
pub fn test_reupload<D: Database>(db: &mut D) {
|
||||
pub fn test_reupload(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
let str_uid2 = "Test B <test_b@example.com>";
|
||||
let tpk = TPKBuilder::new()
|
||||
@@ -324,6 +329,7 @@ pub fn test_reupload<D: Database>(db: &mut D) {
|
||||
|
||||
// upload key
|
||||
db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
|
||||
// verify 1st uid
|
||||
db.set_email_published(&fpr, &email1).unwrap();
|
||||
@@ -343,7 +349,7 @@ pub fn test_reupload<D: Database>(db: &mut D) {
|
||||
assert!(db.by_email(&email2).is_none() ^ db.by_email(&email1).is_none());
|
||||
}
|
||||
|
||||
pub fn test_uid_replacement<D: Database>(db: &mut D) {
|
||||
pub fn test_uid_replacement(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
let tpk1 = TPKBuilder::new().add_userid(str_uid1).generate().unwrap().0;
|
||||
let fpr1 = Fingerprint::try_from(tpk1.fingerprint()).unwrap();
|
||||
@@ -358,7 +364,9 @@ pub fn test_uid_replacement<D: Database>(db: &mut D) {
|
||||
|
||||
// upload both keys
|
||||
db.merge(tpk1).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr1);
|
||||
db.merge(tpk2).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr2);
|
||||
|
||||
// verify 1st uid
|
||||
db.set_email_published(&fpr1, &email1).unwrap();
|
||||
@@ -383,7 +391,7 @@ pub fn test_uid_replacement<D: Database>(db: &mut D) {
|
||||
.userids().len(), 1);
|
||||
}
|
||||
|
||||
pub fn test_uid_deletion<D: Database>(db: &mut D) {
|
||||
pub fn test_uid_deletion(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
let str_uid2 = "Test B <test_b@example.com>";
|
||||
let tpk = TPKBuilder::new()
|
||||
@@ -401,6 +409,7 @@ pub fn test_uid_deletion<D: Database>(db: &mut D) {
|
||||
|
||||
// upload key and verify uids
|
||||
let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -442,7 +451,7 @@ pub fn test_uid_deletion<D: Database>(db: &mut D) {
|
||||
assert_eq!(tpk.subkeys().count(), n_subkeys);
|
||||
}
|
||||
|
||||
pub fn test_subkey_lookup<D: Database>(db: &mut D) {
|
||||
pub fn test_subkey_lookup(db: &mut impl Database, _log_path: &Path) {
|
||||
let tpk = TPKBuilder::new()
|
||||
.add_userid("Testy <test@example.com>")
|
||||
.add_signing_subkey()
|
||||
@@ -454,8 +463,6 @@ pub fn test_subkey_lookup<D: Database>(db: &mut D) {
|
||||
// upload key
|
||||
let _ = db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
|
||||
// upload key
|
||||
let _ = db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
let fpr_primray = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
let fpr_sign: Fingerprint = tpk.keys_all()
|
||||
.signing_capable()
|
||||
@@ -474,7 +481,7 @@ pub fn test_subkey_lookup<D: Database>(db: &mut D) {
|
||||
assert_eq!(raw1, raw2);
|
||||
}
|
||||
|
||||
pub fn test_kid_lookup<D: Database>(db: &mut D) {
|
||||
pub fn test_kid_lookup(db: &mut impl Database, _log_path: &Path) {
|
||||
let tpk = TPKBuilder::new()
|
||||
.add_userid("Testy <test@example.com>")
|
||||
.add_signing_subkey()
|
||||
@@ -503,7 +510,7 @@ pub fn test_kid_lookup<D: Database>(db: &mut D) {
|
||||
assert_eq!(raw1, raw2);
|
||||
}
|
||||
|
||||
pub fn test_upload_revoked_tpk<D: Database>(db: &mut D) {
|
||||
pub fn test_upload_revoked_tpk(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
let str_uid2 = "Test B <test_b@example.com>";
|
||||
let (mut tpk, revocation) = TPKBuilder::new()
|
||||
@@ -513,6 +520,7 @@ pub fn test_upload_revoked_tpk<D: Database>(db: &mut D) {
|
||||
.unwrap();
|
||||
let email1 = Email::from_str(str_uid1).unwrap();
|
||||
let email2 = Email::from_str(str_uid2).unwrap();
|
||||
let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
|
||||
tpk = tpk.merge_packets(vec![revocation.into()]).unwrap();
|
||||
match tpk.revocation_status() {
|
||||
@@ -522,6 +530,7 @@ pub fn test_upload_revoked_tpk<D: Database>(db: &mut D) {
|
||||
|
||||
// upload key
|
||||
let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: true,
|
||||
email_status: vec!(
|
||||
@@ -532,7 +541,7 @@ pub fn test_upload_revoked_tpk<D: Database>(db: &mut D) {
|
||||
}, tpk_status);
|
||||
}
|
||||
|
||||
pub fn test_uid_revocation<D: Database>(db: &mut D) {
|
||||
pub fn test_uid_revocation(db: &mut impl Database, log_path: &Path) {
|
||||
use std::{thread, time};
|
||||
|
||||
let str_uid1 = "Test A <test_a@example.com>";
|
||||
@@ -550,6 +559,7 @@ pub fn test_uid_revocation<D: Database>(db: &mut D) {
|
||||
|
||||
// upload key
|
||||
let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -603,7 +613,7 @@ pub fn test_uid_revocation<D: Database>(db: &mut D) {
|
||||
}
|
||||
|
||||
/* FIXME I couldn't get this to work.
|
||||
pub fn test_uid_revocation_fake<D: Database>(db: &mut D) {
|
||||
pub fn test_uid_revocation_fake(db: &mut D) {
|
||||
use std::{thread, time};
|
||||
|
||||
let str_uid = "Test A <test_a@example.com>";
|
||||
@@ -682,7 +692,7 @@ pub fn test_uid_revocation_fake<D: Database>(db: &mut D) {
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn test_unlink_uid<D: Database>(db: &mut D) {
|
||||
pub fn test_unlink_uid(db: &mut impl Database, log_path: &Path) {
|
||||
let uid = "Test A <test_a@example.com>";
|
||||
let email = Email::from_str(uid).unwrap();
|
||||
|
||||
@@ -696,6 +706,7 @@ pub fn test_unlink_uid<D: Database>(db: &mut D) {
|
||||
|
||||
// Create a 2nd key with same uid, and revoke the uid.
|
||||
let tpk_evil = TPKBuilder::new().add_userid(uid).generate().unwrap().0;
|
||||
let fpr_evil = Fingerprint::try_from(tpk_evil.fingerprint()).unwrap();
|
||||
let sig = {
|
||||
let uid = tpk_evil.userids()
|
||||
.find(|b| b.userid().value() == uid.as_bytes()).unwrap();
|
||||
@@ -715,6 +726,7 @@ pub fn test_unlink_uid<D: Database>(db: &mut D) {
|
||||
assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation);
|
||||
let tpk_evil = tpk_evil.merge_packets(vec![sig.into()]).unwrap();
|
||||
let tpk_status = db.merge(tpk_evil).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr_evil);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -737,7 +749,7 @@ pub fn get_userids(armored: &str) -> Vec<UserID> {
|
||||
|
||||
// If multiple keys have the same email address, make sure things work
|
||||
// as expected.
|
||||
pub fn test_same_email_1<D: Database>(db: &mut D) {
|
||||
pub fn test_same_email_1(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "A <test@example.com>";
|
||||
let tpk1 = TPKBuilder::new()
|
||||
.add_userid(str_uid1)
|
||||
@@ -760,6 +772,7 @@ pub fn test_same_email_1<D: Database>(db: &mut D) {
|
||||
|
||||
// upload keys.
|
||||
let tpk_status1 = db.merge(tpk1).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr1);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -768,6 +781,7 @@ pub fn test_same_email_1<D: Database>(db: &mut D) {
|
||||
unparsed_uids: 0,
|
||||
}, tpk_status1);
|
||||
let tpk_status2 = db.merge(tpk2.clone()).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr2);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -814,6 +828,7 @@ pub fn test_same_email_1<D: Database>(db: &mut D) {
|
||||
assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation);
|
||||
let tpk2 = tpk2.merge_packets(vec![sig.into()]).unwrap();
|
||||
let tpk_status2 = db.merge(tpk2).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr2);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -829,7 +844,7 @@ pub fn test_same_email_1<D: Database>(db: &mut D) {
|
||||
|
||||
// If a key has multiple user ids with the same email address, make
|
||||
// sure things still work.
|
||||
pub fn test_same_email_2<D: Database>(db: &mut D) {
|
||||
pub fn test_same_email_2(db: &mut impl Database, log_path: &Path) {
|
||||
use std::{thread, time};
|
||||
|
||||
let str_uid1 = "A <test@example.com>";
|
||||
@@ -847,6 +862,7 @@ pub fn test_same_email_2<D: Database>(db: &mut D) {
|
||||
|
||||
// upload key
|
||||
let tpk_status = db.merge(tpk.clone()).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
|
||||
// verify uid1
|
||||
assert_eq!(TpkStatus {
|
||||
@@ -883,6 +899,7 @@ pub fn test_same_email_2<D: Database>(db: &mut D) {
|
||||
assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation);
|
||||
let tpk = tpk.merge_packets(vec![sig.into()]).unwrap();
|
||||
let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
@@ -899,7 +916,7 @@ pub fn test_same_email_2<D: Database>(db: &mut D) {
|
||||
vec![ uid1.clone() ]);
|
||||
}
|
||||
|
||||
pub fn test_bad_uids<D: Database>(db: &mut D) {
|
||||
pub fn test_bad_uids(db: &mut impl Database, log_path: &Path) {
|
||||
let str_uid1 = "foo@bar.example <foo@bar.example>";
|
||||
let str_uid2 = "A <test@example.com>";
|
||||
let str_uid3 = "lalalalaaaaa";
|
||||
@@ -911,33 +928,39 @@ pub fn test_bad_uids<D: Database>(db: &mut D) {
|
||||
.unwrap()
|
||||
.0;
|
||||
let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
let email1 = Email::from_str(str_uid1).unwrap();
|
||||
let email2 = Email::from_str(str_uid2).unwrap();
|
||||
|
||||
let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
(email1.clone(), EmailAddressStatus::NotPublished),
|
||||
(email2.clone(), EmailAddressStatus::NotPublished),
|
||||
),
|
||||
unparsed_uids: 2,
|
||||
unparsed_uids: 1,
|
||||
}, tpk_status);
|
||||
|
||||
db.set_email_published(&fpr, &email2).unwrap();
|
||||
|
||||
let tpk_status = db.get_tpk_status(&fpr, &vec!(email2.clone())).unwrap();
|
||||
let tpk_status = db.get_tpk_status(&fpr, &vec!(email1.clone(),
|
||||
email2.clone())).unwrap();
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: false,
|
||||
email_status: vec!(
|
||||
(email1.clone(), EmailAddressStatus::NotPublished),
|
||||
(email2.clone(), EmailAddressStatus::Published),
|
||||
),
|
||||
unparsed_uids: 2,
|
||||
unparsed_uids: 1,
|
||||
}, tpk_status);
|
||||
}
|
||||
|
||||
pub fn test_no_selfsig<D: Database>(db: &mut D) {
|
||||
pub fn test_no_selfsig(db: &mut impl Database, log_path: &Path) {
|
||||
let (mut tpk, revocation) = TPKBuilder::new()
|
||||
.generate()
|
||||
.unwrap();
|
||||
let fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
|
||||
// don't allow upload of naked key
|
||||
assert!(db.merge(tpk.clone()).is_err());
|
||||
@@ -945,9 +968,21 @@ pub fn test_no_selfsig<D: Database>(db: &mut D) {
|
||||
// with revocation, it's ok
|
||||
tpk = tpk.merge_packets(vec![revocation.into()]).unwrap();
|
||||
let tpk_status = db.merge(tpk).unwrap().into_tpk_status();
|
||||
check_log_entry(log_path, &fpr);
|
||||
assert_eq!(TpkStatus {
|
||||
is_revoked: true,
|
||||
email_status: vec!(),
|
||||
unparsed_uids: 0,
|
||||
}, tpk_status);
|
||||
}
|
||||
|
||||
fn check_log_entry(log_path: &Path, fpr: &Fingerprint) {
|
||||
let log_data = fs::read_to_string(log_path).unwrap();
|
||||
let last_entry = log_data
|
||||
.lines()
|
||||
.last().unwrap()
|
||||
.split(" ")
|
||||
.last().unwrap();
|
||||
assert_eq!(last_entry, fpr.to_string());
|
||||
|
||||
}
|
||||
|
BIN
dist/assets/fonts/roboto-v18-latin-300.eot
vendored
BIN
dist/assets/fonts/roboto-v18-latin-300.eot
vendored
Binary file not shown.
BIN
dist/assets/fonts/roboto-v18-latin-300.ttf
vendored
BIN
dist/assets/fonts/roboto-v18-latin-300.ttf
vendored
Binary file not shown.
BIN
dist/assets/fonts/roboto-v18-latin-300.woff
vendored
BIN
dist/assets/fonts/roboto-v18-latin-300.woff
vendored
Binary file not shown.
BIN
dist/assets/fonts/roboto-v18-latin-300.woff2
vendored
BIN
dist/assets/fonts/roboto-v18-latin-300.woff2
vendored
Binary file not shown.
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.eot
vendored
Normal file
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.eot
vendored
Normal file
Binary file not shown.
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.ttf
vendored
Normal file
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.ttf
vendored
Normal file
Binary file not shown.
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.woff
vendored
Normal file
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.woff
vendored
Normal file
Binary file not shown.
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.woff2
vendored
Normal file
BIN
dist/assets/fonts/roboto-v20-latin_latin-ext-300.woff2
vendored
Normal file
Binary file not shown.
18
dist/assets/img/atom.svg
vendored
Normal file
18
dist/assets/img/atom.svg
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" id="RSSicon" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg">
|
||||
<stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/>
|
||||
<stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/>
|
||||
<stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/>
|
||||
<stop offset="1.0" stop-color="#D95B29"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/>
|
||||
<rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/>
|
||||
<rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)"/>
|
||||
<circle cx="68" cy="189" r="24" fill="#FFF"/>
|
||||
<path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/>
|
||||
<path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
dist/assets/img/gpgtools.png
vendored
Normal file
BIN
dist/assets/img/gpgtools.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
BIN
dist/assets/img/stats-addresses-2019-09-12.png
vendored
Normal file
BIN
dist/assets/img/stats-addresses-2019-09-12.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
dist/assets/img/stats-addresses-2019-11-12.png
vendored
Normal file
BIN
dist/assets/img/stats-addresses-2019-11-12.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
1
dist/assets/js/upload-verify.js
vendored
Normal file
1
dist/assets/js/upload-verify.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
document.getElementById("postform").submit();
|
14
dist/assets/js/upload.js
vendored
Normal file
14
dist/assets/js/upload.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
var body = document.getElementsByTagName("body")[0];
|
||||
|
||||
body.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, false);
|
||||
|
||||
body.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.dataTransfer) {
|
||||
document.getElementById('keytext').files = e.dataTransfer.files;
|
||||
}
|
||||
}, false);
|
40
dist/assets/site.css
vendored
40
dist/assets/site.css
vendored
@@ -9,13 +9,13 @@ body {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('fonts/roboto-v18-latin-300.eot');
|
||||
src: url('fonts/roboto-v20-latin_latin-ext-300.eot');
|
||||
src: local('Roboto Light'), local('Roboto-Light'),
|
||||
url('fonts/roboto-v18-latin-300.eot?#iefix') format('embedded-opentype'),
|
||||
url('fonts/roboto-v18-latin-300.woff2') format('woff2'),
|
||||
url('fonts/roboto-v18-latin-300.woff') format('woff'),
|
||||
url('fonts/roboto-v18-latin-300.ttf') format('truetype'),
|
||||
url('fonts/roboto-v18-latin-300.svg#Roboto') format('svg');
|
||||
url('fonts/roboto-v20-latin_latin-ext-300.eot?#iefix') format('embedded-opentype'),
|
||||
url('fonts/roboto-v20-latin_latin-ext-300.woff2') format('woff2'),
|
||||
url('fonts/roboto-v20-latin_latin-ext-300.woff') format('woff'),
|
||||
url('fonts/roboto-v20-latin_latin-ext-300.ttf') format('truetype'),
|
||||
url('fonts/roboto-v20-latin_latin-ext-300.svg#Roboto') format('svg');
|
||||
}
|
||||
|
||||
tt {
|
||||
@@ -43,11 +43,23 @@ a.brand {
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
h3:target {
|
||||
.usage h2 div {
|
||||
display: inline-block;
|
||||
width: 2.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.usage h2 div img {
|
||||
display: inline-block;
|
||||
height: 2em;
|
||||
vertical-align: -30%;
|
||||
}
|
||||
|
||||
h4:target, h3:target {
|
||||
background-color: #ffa;
|
||||
}
|
||||
|
||||
h3 a, h3 a:visited {
|
||||
h4 a, h4 a:visited, h3 a, h3 a:visited {
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
@@ -221,6 +233,7 @@ span.fingerprint {
|
||||
span.email {
|
||||
font-family: monospace;
|
||||
font-size: large;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.debug_link {
|
||||
@@ -254,3 +267,14 @@ span.email {
|
||||
.attribution a {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input.textbutton {
|
||||
padding: 0px;
|
||||
border: none;
|
||||
background: none;
|
||||
margin: 0px;
|
||||
cursor: pointer;
|
||||
color: blue;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
25
dist/email-templates/manage.htm.hbs
vendored
Normal file
25
dist/email-templates/manage.htm.hbs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="{{lang}}">
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>{{ text "Manage your key on {{domain}}" rerender }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
{{ text "Hi," }}
|
||||
<p>
|
||||
{{ text "This is an automated message from <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: #333\">{{domain}}</a>." rerender }}
|
||||
{{ text "If you didn't request this message, please ignore it." }}
|
||||
<p>
|
||||
{{ text "OpenPGP key: <tt>{{primary_fp}}</tt>" rerender }}
|
||||
<p>
|
||||
{{ text "To manage and delete listed addresses on this key, please follow the link below:" }}
|
||||
<p>
|
||||
<a rel="nofollow" href="{{uri}}">{{uri}}</a>
|
||||
<p>
|
||||
{{ text "You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</a>." rerender }}
|
||||
<p>
|
||||
<a href="{{base_uri}}">{{base_uri}}</a><br />
|
||||
{{ text "distributing OpenPGP keys since 2019" }}
|
||||
</body>
|
||||
</html>
|
17
dist/email-templates/manage.txt.hbs
vendored
Normal file
17
dist/email-templates/manage.txt.hbs
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{{ text "Hi," }}
|
||||
|
||||
{{ text "This is an automated message from {{domain}}." rerender }}
|
||||
{{ text "If you didn't request this message, please ignore it." }}
|
||||
|
||||
{{ text "OpenPGP key: {{primary_fp}}" rerender }}
|
||||
|
||||
{{ text "To manage and delete listed addresses on this key, please follow the link below:" }}
|
||||
|
||||
{{ uri }}
|
||||
|
||||
{{ text "You can find more info at {{base_uri}}/about" rerender }}
|
||||
|
||||
--
|
||||
|
||||
{{ base_uri }}
|
||||
{{ text "distributing OpenPGP keys since 2019" }}
|
25
dist/email-templates/verify.htm.hbs
vendored
Normal file
25
dist/email-templates/verify.htm.hbs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="{{lang}}">
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>{{ text "Verify {{userid}} for your key on {{domain}}" rerender }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
{{ text "Hi," }}
|
||||
<p>
|
||||
{{ text "This is an automated message from <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: #333\">{{domain}}</a>." rerender }}
|
||||
{{ text "If you didn't request this message, please ignore it." }}
|
||||
<p>
|
||||
{{ text "OpenPGP key: <tt>{{primary_fp}}</tt>" rerender }}
|
||||
<p>
|
||||
{{ text "To let others find this key from your email address \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", please click the link below:" rerender }}
|
||||
<p>
|
||||
<a rel="nofollow" href="{{uri}}">{{uri}}</a>
|
||||
<p>
|
||||
{{ text "You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</a>." rerender }}
|
||||
<p>
|
||||
<a href="{{base_uri}}">{{base_uri}}</a><br />
|
||||
{{ text "distributing OpenPGP keys since 2019" }}
|
||||
</body>
|
||||
</html>
|
17
dist/email-templates/verify.txt.hbs
vendored
Normal file
17
dist/email-templates/verify.txt.hbs
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{{ text "Hi," }}
|
||||
|
||||
{{ text "This is an automated message from {{domain}}." rerender }}
|
||||
{{ text "If you didn't request this message, please ignore it." }}
|
||||
|
||||
{{ text "OpenPGP key: {{primary_fp}}" rerender }}
|
||||
|
||||
{{ text "To let others find this key from your email address \"{{userid}}\",\nplease follow the link below:" rerender }}
|
||||
|
||||
{{uri}}
|
||||
|
||||
{{ text "You can find more info at {{base_uri}}/about" rerender }}
|
||||
|
||||
--
|
||||
|
||||
{{ base_uri }}
|
||||
{{ text "distributing OpenPGP keys since 2019" }}
|
26
dist/email-templates/welcome.htm.hbs
vendored
Normal file
26
dist/email-templates/welcome.htm.hbs
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="{{lang}}">
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Your key upload on {{domain}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Hi,
|
||||
<p>
|
||||
This is an automated message from <a href="{{base_uri}}" style="text-decoration:none; color: #333">{{domain}}</a>.
|
||||
If you didn't upload your key there, please ignore this message.
|
||||
<p>
|
||||
OpenPGP key: <tt>{{primary_fp}}</tt>
|
||||
<p>
|
||||
This key was just uploaded for the first time, and is now published without identity information. If you want to allow others to find this key by e-mail address, please follow this link:
|
||||
<p>
|
||||
<a rel="nofollow" href="{{uri}}">{{uri}}</a>
|
||||
<p>
|
||||
You can find more info at <a href="{{base_uri}}/about">{{domain}}/about</a>.
|
||||
<p>
|
||||
<a href="{{base_uri}}">{{base_uri}}</a><br />
|
||||
distributing OpenPGP keys since 2019
|
||||
</body>
|
||||
</html>
|
||||
|
19
dist/email-templates/welcome.txt.hbs
vendored
Normal file
19
dist/email-templates/welcome.txt.hbs
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Hi,
|
||||
|
||||
This is an automated message from {{domain}}.
|
||||
If you didn't upload your key, please ignore this message.
|
||||
|
||||
OpenPGP key: {{primary_fp}}
|
||||
|
||||
This key was just uploaded for the first time, and is now published without
|
||||
identity information. If you want to allow others to find this key by e-mail
|
||||
address, please follow this link:
|
||||
|
||||
{{uri}}
|
||||
|
||||
You can find more info at {{base_uri}}/about
|
||||
|
||||
--
|
||||
|
||||
{{ base_uri }}
|
||||
distributing OpenPGP keys since 2019
|
2
dist/errors-static/400-pks-invalid.htm
vendored
Normal file
2
dist/errors-static/400-pks-invalid.htm
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<p><strong>Error 400</strong> Invalid request</p>
|
||||
<p>See https://keys.openpgp.org/about/api</p>
|
2
dist/errors-static/400-vks-invalid.htm
vendored
Normal file
2
dist/errors-static/400-vks-invalid.htm
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<p><strong>Error 400</strong> Invalid request</p>
|
||||
<p>See https://keys.openpgp.org/about/api</p>
|
1
dist/errors-static/404-by-email.htm
vendored
Normal file
1
dist/errors-static/404-by-email.htm
vendored
Normal file
@@ -0,0 +1 @@
|
||||
No key found for this email address.
|
1
dist/errors-static/404-by-fpr.htm
vendored
Normal file
1
dist/errors-static/404-by-fpr.htm
vendored
Normal file
@@ -0,0 +1 @@
|
||||
No key found for this fingerprint.
|
1
dist/errors-static/404-by-keyid.htm
vendored
Normal file
1
dist/errors-static/404-by-keyid.htm
vendored
Normal file
@@ -0,0 +1 @@
|
||||
No key found for this key id.
|
2
dist/errors-static/429-rate-limit-pks-index.htm
vendored
Normal file
2
dist/errors-static/429-rate-limit-pks-index.htm
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<p><strong>Error 429</strong> Too many requests</p>
|
||||
<p>See https://keys.openpgp.org/about/api#rate-limiting</p>
|
2
dist/errors-static/429-rate-limit-vks-email.htm
vendored
Normal file
2
dist/errors-static/429-rate-limit-vks-email.htm
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<p><strong>Error 429</strong> Too many requests</p>
|
||||
<p>See https://keys.openpgp.org/about/api#rate-limiting</p>
|
2
dist/templates/400-plain.html.hbs
vendored
2
dist/templates/400-plain.html.hbs
vendored
@@ -1 +1 @@
|
||||
Bad request: {{error}}
|
||||
Bad request: {{ page/error }}
|
||||
|
5
dist/templates/400.html.hbs
vendored
Normal file
5
dist/templates/400.html.hbs
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{{#> layout }}
|
||||
<h3>{{ text "There was an error with your request:" }}</h3>
|
||||
|
||||
<p>{{ page/error }}</p>
|
||||
{{/layout}}
|
6
dist/templates/500.html.hbs
vendored
6
dist/templates/500.html.hbs
vendored
@@ -1,7 +1,7 @@
|
||||
{{#> layout }}
|
||||
<h2>Error</h2>
|
||||
<h2>{{ text "Error" }}</h2>
|
||||
|
||||
<p>Looks like something went wrong :(</p>
|
||||
<p>{{ text "Looks like something went wrong :(" }}</p>
|
||||
|
||||
<p><strong>Error:</strong> {{internal_error}}</p>
|
||||
<p>{{ text "Error message: {{ internal_error }}" rerender }}</p>
|
||||
{{/layout}}
|
||||
|
23
dist/templates/about/about.html.hbs
vendored
23
dist/templates/about/about.html.hbs
vendored
@@ -21,7 +21,7 @@
|
||||
<ul>
|
||||
<li><strong>Identity information</strong> describes the parts of
|
||||
a key that identify its owner, also known as "User IDs".
|
||||
A User ID typically includes a name and an e-mail address.
|
||||
A User ID typically includes a name and an email address.
|
||||
</li>
|
||||
<li><strong>Non-identity information</strong> is all the technical
|
||||
information about the key itself. This includes the large numbers
|
||||
@@ -54,9 +54,9 @@
|
||||
The <strong>identity information</strong> in an OpenPGP key
|
||||
is only distributed with consent.
|
||||
It contains personal data, and is not strictly necessary for
|
||||
a key to be used for encryption or signature verification. Once the
|
||||
owner gives consent by verifying their e-mail address, the key can
|
||||
be found by everyone via search by address.
|
||||
a key to be used for encryption or signature verification.
|
||||
Once the owner gives consent by verifying their email address,
|
||||
the key can be found via search by address.
|
||||
</p>
|
||||
|
||||
<h3 id="community">Community and platform</h3>
|
||||
@@ -66,7 +66,7 @@
|
||||
You can talk to us in
|
||||
#hagrid on Freenode IRC,
|
||||
also reachable as #hagrid:stratum0.org on Matrix.
|
||||
Of course you can also reach us via e-mail,
|
||||
Of course you can also reach us via email,
|
||||
at <tt>support at keys dot openpgp dot org</tt>.
|
||||
The folks who are running this come
|
||||
from various projects in the OpenPGP ecosystem,
|
||||
@@ -74,13 +74,12 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Technically, <tt>keys.openpgp.org</tt> runs on the
|
||||
<a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a>
|
||||
keyserver software, which is based on
|
||||
<a href="https://sequoia-pgp.org">Sequoia-PGP</a>. We are hosted on the
|
||||
<a href="https://eclips.is" target="_blank">eclips.is</a> platform,
|
||||
a hosting provider focused on Internet Freedom projects, run by
|
||||
<a href="https://greenhost.net/" target="_blank">Greenhost</a>.
|
||||
Technically,
|
||||
<tt>keys.openpgp.org</tt> runs on the <a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a> keyserver software,
|
||||
which is based on <a href="https://sequoia-pgp.org">Sequoia-PGP</a>.
|
||||
We are running on <a href="https://eclips.is" target="_blank">eclips.is</a>,
|
||||
a hosting platform focused on Internet Freedom projects,
|
||||
which is managed by <a href="https://greenhost.net/" target="_blank">Greenhost</a>.
|
||||
</p>
|
||||
</div>
|
||||
{{/layout}}
|
||||
|
54
dist/templates/about/api.html.hbs
vendored
54
dist/templates/about/api.html.hbs
vendored
@@ -37,9 +37,9 @@
|
||||
<li>
|
||||
<tt>GET /vks/v1/by-email/<URI-ENCODED EMAIL-ADDRESS></tt>
|
||||
<p>
|
||||
Retrieves the key with the given <tt>E-Mail Address</tt>.
|
||||
Retrieves the key with the given <tt>Email Address</tt>.
|
||||
Only exact matches are accepted.
|
||||
Lookup by e-mail address requires opt-in by the owner of the e-mail address.
|
||||
Lookup by email address requires opt-in by the owner of the email address.
|
||||
The returned key is ASCII Armored, and has a content-type of <code>application/pgp-keys</code>.
|
||||
</p>
|
||||
</li>
|
||||
@@ -68,7 +68,7 @@
|
||||
<code>published</code>,
|
||||
<code>revoked</code>, or
|
||||
<code>pending</code>,
|
||||
indicating the status of this e-mail address.
|
||||
indicating the status of this email address.
|
||||
</p>
|
||||
|
||||
<div class="example">
|
||||
@@ -99,23 +99,23 @@
|
||||
<tt>POST /vks/v1/request-verify</tt>
|
||||
<p>
|
||||
A key that has been uploaded
|
||||
can be made discoverable by one or more of its e-mail addresses
|
||||
can be made discoverable by one or more of its email addresses
|
||||
by proving ownership of the address
|
||||
via a verification e-mail.
|
||||
via a verification email.
|
||||
This endpoint requests verification
|
||||
for one or more e-mail addresses.
|
||||
for one or more email addresses.
|
||||
</p>
|
||||
<p>
|
||||
The body of the request must be <code>application/json</code>.
|
||||
The JSON data must include the opaque <code>token</code> value
|
||||
(obtained via <tt>/vks/v1/upload</tt>)
|
||||
and an <code>addresses</code> field,
|
||||
which is a list of e-mail addresses (not full User IDs)
|
||||
which is a list of email addresses (not full User IDs)
|
||||
to request verification mails for.
|
||||
It can optionally include a <code>locale</code> field,
|
||||
which is list of locales,
|
||||
ordered by preference,
|
||||
which to use for the verification e-mail.
|
||||
which to use for the verification email.
|
||||
The reply will be the same as for the <tt>/vks/v1/upload</tt> endpoint,
|
||||
with addresses marked as <code>pending</code> where a verification email
|
||||
has been sent.
|
||||
@@ -183,6 +183,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 id="rate-limiting"><a href="#rate-limiting">Rate Limiting</a></h3>
|
||||
|
||||
<p>
|
||||
Requests to the <span class="brand">keys.openpgp.org</span> API are rate
|
||||
limited:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Requests by fingerprint or key id are limited to a rate of five requests
|
||||
per second. Excessive requests are delayed to match this rate. Short
|
||||
bursts are allowed.
|
||||
</li>
|
||||
<li>Requests by email address are limited to one request per minute.
|
||||
Excessive requests will fail with <tt>error 429</tt>. Short bursts are
|
||||
allowed.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>HTTP Keyserver Protocol (HKP) Interface</h2>
|
||||
<p>
|
||||
Hagrid implements a subset of
|
||||
@@ -238,13 +255,16 @@
|
||||
<code>keytext</code> must be the keys to submit,
|
||||
which must be ASCII Armored.
|
||||
More than one key may be submitted in one request.
|
||||
For verification of e-mail addresses,
|
||||
For verification of email addresses,
|
||||
the <tt>/vks/v1/upload</tt> endpoint
|
||||
must be used instead.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a href="#rate-limiting">Rate limiting</a> applies as for the VKS interface
|
||||
above.
|
||||
|
||||
<h4>Limitations</h4>
|
||||
|
||||
<p>
|
||||
@@ -253,14 +273,14 @@
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>No support for <code>op=vindex</code>,</li>
|
||||
<li>only exact matches for user IDs are returned (i.e. <code>exact=on</code> is
|
||||
always assumed),</li>
|
||||
<li>the <code>fingerprint</code> variable is ignored,</li>
|
||||
<li>the <code>nm</code> option is ignored,</li>
|
||||
<li><code>op=index</code> returns either one or no keys,</li>
|
||||
<li>uploads are restricted to 1 MiB,</li>
|
||||
<li>all packets that aren't public keys, user IDs or signatures are filtered out.</li>
|
||||
<li>No support for <code>op=vindex</code>.</li>
|
||||
<li>Only exact matches by email address, fingerprint or long key id are returned.</li>
|
||||
<li>All requests return either one or no keys.</li>
|
||||
<li>The expiration date field in <code>op=index</code> is left blank (discussion <a target="_blank" href="https://gitlab.com/hagrid-keyserver/hagrid/issues/134">here</a>).</li>
|
||||
<li>All parameters and options other than <code>op</code> and <code>search</code> are ignored.</li>
|
||||
<li>Output is always machine readable (i.e. <code>options=mr</code> is always assumed).</li>
|
||||
<li>Uploads are restricted to 1 MiB.</li>
|
||||
<li>All packets that aren't public keys, user IDs or signatures are filtered out.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
59
dist/templates/about/faq.html.hbs
vendored
59
dist/templates/about/faq.html.hbs
vendored
@@ -35,7 +35,7 @@
|
||||
<li>
|
||||
Federation with open participation requires all data to be public.
|
||||
This significantly impacts the privacy of our users, because it
|
||||
allows anyone to scrape a list of all e-mail addresses.
|
||||
allows anyone to scrape a list of all email addresses.
|
||||
</li>
|
||||
<li>
|
||||
Servers run as a hobby by casual administrators do not meet our
|
||||
@@ -44,25 +44,25 @@
|
||||
</ol>
|
||||
|
||||
<h3 id="non-email-uids"><a href="#non-email-uids">Why is there no support
|
||||
for identities that aren't e-mail addresses?</a></h3>
|
||||
for identities that aren't email addresses?</a></h3>
|
||||
|
||||
<p>
|
||||
We require explicit consent to distribute identity information.
|
||||
Identities that aren't e-mail addresses, such as pictures or website
|
||||
Identities that aren't email addresses, such as pictures or website
|
||||
URLs, offer no simple way for us to acquire this consent.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note: Some OpenPGP software creates keys with incorrectly formatted
|
||||
e-mail addresses. These addresses might not be recognized correctly on
|
||||
email addresses. These addresses might not be recognized correctly on
|
||||
<span class="brand">keys.openpgp.org</span>.
|
||||
</p>
|
||||
|
||||
<h3 id="verify-multiple"><a href="#verify-multiple">Can I verify more than
|
||||
one key for some e-mail address?</a></h3>
|
||||
one key for some email address?</a></h3>
|
||||
|
||||
<p>
|
||||
An e-mail address can only be associated with a single key.
|
||||
An email address can only be associated with a single key.
|
||||
When an address is verified for a new key,
|
||||
it will no longer appear in any key
|
||||
for which it was previously verified.
|
||||
@@ -71,16 +71,16 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This means a search by e-mail address
|
||||
This means a search by email address
|
||||
will only return a single key,
|
||||
not multiple candidates.
|
||||
This eliminates an impossible choice for the user
|
||||
("Which key is the right one?"),
|
||||
and makes key discovery by e-mail much more convenient.
|
||||
and makes key discovery by email much more convenient.
|
||||
</p>
|
||||
|
||||
<h3 id="email-protection"><a href="#email-protection">What do you do to
|
||||
protect outgoing verification e-mails?</a></h3>
|
||||
protect outgoing verification emails?</a></h3>
|
||||
|
||||
<p>
|
||||
We use a modern standard called
|
||||
@@ -88,14 +88,14 @@
|
||||
combined with
|
||||
<a href="https://starttls-everywhere.org/" target="_blank">STARTTLS Everywhere</a>
|
||||
by the EFF,
|
||||
to make sure verification e-mails are sent out securely.
|
||||
to make sure verification emails are sent out securely.
|
||||
This protects against eavesdropping and interception during delivery.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The MTA-STS mechanism depends on correctly configured e-mail servers.
|
||||
The MTA-STS mechanism depends on correctly configured email servers.
|
||||
You can <a href="https://www.hardenize.com/">run this test</a>
|
||||
to see if your e-mail provider supports it.
|
||||
to see if your email provider supports it.
|
||||
If the "MTA-STS" entry on the left isn't a green checkmark,
|
||||
please ask your provider to update their configuration.
|
||||
</p>
|
||||
@@ -148,9 +148,9 @@
|
||||
|
||||
<p>
|
||||
The <span class="brand">keys.openpgp.org</span> service is meant for key
|
||||
distribution and discovery, not as a de-facto CA. Client implementations
|
||||
that want to offer verified communication should rely on their own trust
|
||||
model.
|
||||
distribution and discovery, not as a de facto certification authority.
|
||||
Client implementations that want to offer verified communication should
|
||||
rely on their own trust model.
|
||||
</p>
|
||||
|
||||
<h3 id="revoked-uids"><a href="#revoked-uids">Why are revoked identities not
|
||||
@@ -158,7 +158,7 @@
|
||||
|
||||
<p>
|
||||
When an OpenPGP key marks one of its identities as revoked, this
|
||||
identity should no longer be considered valid for the key. And this
|
||||
identity should no longer be considered valid for the key, and this
|
||||
information should ideally be distributed to all OpenPGP clients that
|
||||
already know about the newly revoked identity.
|
||||
</p>
|
||||
@@ -177,6 +177,25 @@
|
||||
we can.
|
||||
</p>
|
||||
|
||||
<h3 id="search-substring"><a href="#search-substring">Why isn't it possible to search by part of an email address, like just the domain?</a></h3>
|
||||
|
||||
<p>
|
||||
Some keyservers support search for keys by part of an email address.
|
||||
This allows discovery not only of keys, but also of addresses, with a query like "keys for addresses at gmail dot com".
|
||||
This effectively puts the addresses of all keys on those keyservers into a public listing.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Searches by email address on <span class="brand">keys.openpgp.org</span> return only exact matches.
|
||||
That way, users can discover keys for addresses they already know, but not new addresses.
|
||||
This ensures malicious users and spammers can't obtain a list of all addresses on the server.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We made this restriction a part of our <a href="/about/privacy">privacy policy</a>,
|
||||
which means we can't change it without asking for user consent.
|
||||
</p>
|
||||
|
||||
<h3 id="tor"><a href="#tor">Do you support Tor?</a></h3>
|
||||
|
||||
<p>
|
||||
@@ -184,13 +203,13 @@
|
||||
If you have Tor installed,
|
||||
you can reach <span class="brand">keys.openpgp.org</span> anonymously
|
||||
as an
|
||||
<a href="https://en.wikipedia.org/wiki/Tor_(anonymity_network)#Onion_services" target="_blank">onion service</a>:
|
||||
<a href="https://support.torproject.org/onionservices/#onionservices-2" target="_blank">onion service</a>:
|
||||
<br />
|
||||
<a href="http://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion">zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion</a>
|
||||
</p>
|
||||
|
||||
<h3 id="encrypt-verification-emails"><a href="#encrypt-verification-emails">
|
||||
Why not encrypt verification e-mails?</a></h3>
|
||||
Why not encrypt verification emails?</a></h3>
|
||||
|
||||
Various reasons:
|
||||
<ol>
|
||||
@@ -214,8 +233,8 @@
|
||||
to process the key:
|
||||
</p>
|
||||
<blockquote>
|
||||
$ gpg --receive-keys A2604867523C7ED8<br />
|
||||
gpg: key A2604867523C7ED8: no user ID
|
||||
$ gpg --receive-keys EB85BB5FA33A75E15E944E63F231550C4F47E38E<br />
|
||||
gpg: key EB85BB5FA33A75E15E944E63F231550C4F47E38E: no user ID
|
||||
</blockquote>
|
||||
<p>
|
||||
We are working with the GnuPG team to resolve this problem.
|
||||
|
253
dist/templates/about/news.html.hbs
vendored
253
dist/templates/about/news.html.hbs
vendored
@@ -2,10 +2,238 @@
|
||||
<div class="about">
|
||||
<center><h2><a href="/about">About</a> | News | <a href="/about/usage">Usage</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Stats</a> | <a href="/about/privacy">Privacy</a></h2></center>
|
||||
|
||||
<h3>
|
||||
<h2 id="2019-11-12-celebrating-100k">
|
||||
<div style="float: right; font-size: small; line-height: 2em;">2019-11-12 📅</div>
|
||||
<a style="color: black;" href="/about/news#2019-11-12-celebrating-100k">Celebrating 100.000 verified addresses! 📈</a>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
Five months ago, we launched this service.
|
||||
And just today, we have reached a remarkable milestone:
|
||||
|
||||
<center style="margin-top: 2em; margin-bottom: 2em;">
|
||||
<img src="/assets/img/stats-addresses-2019-11-12.png" style="padding: 1px; border: 1px solid gray;" /><br />
|
||||
<strong>One hundred thousand verified email addresses!</strong>
|
||||
</center>
|
||||
|
||||
<p>
|
||||
Thanks to everyone using this service!
|
||||
And thanks especially to those who have provided us with feedback,
|
||||
translations, or even code contributions!
|
||||
|
||||
<p>
|
||||
A few updates on things we've been working on:
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
This news page is now available as an <strong><a target="_blank" href="/atom.xml">atom feed <img src="/assets/img/atom.svg" style="height: 0.8em;" /></a></strong>.
|
||||
</li>
|
||||
<li>
|
||||
We have been working on
|
||||
a <strong><a target="_blank" href="https://gitlab.com/hagrid-keyserver/hagrid/issues/131">new mechanism to refresh keys</a></strong>
|
||||
that better protects the user's privacy.
|
||||
</li>
|
||||
<li>
|
||||
Work on <strong>localization</strong> is in full swing!
|
||||
we hope to have some languages ready for deployment soon.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
If you would like to see <span class="brand">keys.openpgp.org</span>
|
||||
translated into your native language,
|
||||
please <a target="_blank" href="https://www.transifex.com/otf/hagrid/">join the translation team</a>
|
||||
over on Transifex.
|
||||
We would appreciate help especially for <strong>Russian</strong>, <strong>Italian</strong>, <strong>Polish</strong> and <strong>Dutch</strong>.
|
||||
|
||||
|
||||
<p>
|
||||
That's all, keeping this one short!
|
||||
<span style="font-size: x-large;">👍️</span>
|
||||
|
||||
<hr style="margin-top: 2em; margin-bottom: 2em;" />
|
||||
|
||||
|
||||
<h2 id="2019-09-12-three-months-later">
|
||||
<div style="float: right; font-size: small; line-height: 2em;">2019-09-12 📅</div>
|
||||
<a style="color: black;" href="/about/news#2019-09-12-three-months-later">Three months after launch ✨</a>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
It has been three months now
|
||||
<a href="/about/news#2019-06-12-launch">since we launched</a>
|
||||
<span class="brand">keys.openpgp.org</span>.
|
||||
We are happy to report:
|
||||
It has been a resounding success!
|
||||
🥳
|
||||
|
||||
<h4>Adoption in clients</h4>
|
||||
|
||||
<p>
|
||||
The
|
||||
<span class="brand">keys.openpgp.org</span>
|
||||
keyserver has been received very well by users,
|
||||
and clients are adopting it rapidly.
|
||||
It is now used by default in
|
||||
<a href="https://gpgtools.org/" target="_blank">GPGTools</a>,
|
||||
<a href="https://enigmail.net/" target="_blank">Enigmail</a>,
|
||||
<a href="https://www.openkeychain.org/" target="_blank">OpenKeychain</a>,
|
||||
<a href="https://github.com/firstlookmedia/gpgsync" target="_blank">GPGSync</a>,
|
||||
Debian,
|
||||
NixOS,
|
||||
and others.
|
||||
Many tutorials have also been updated,
|
||||
pointing users our way.
|
||||
|
||||
<p>
|
||||
At the time of writing,
|
||||
more than 70.000 email addresses
|
||||
have been verified.
|
||||
|
||||
<center style="margin-top: 2em; margin-bottom: 2em;">
|
||||
<img src="/assets/img/stats-addresses-2019-09-12.png" style="padding: 1px; border: 1px solid gray;" /><br />
|
||||
<span style="font-size: smaller;">If that isn't a promising curve, I don't know what is :)</span>
|
||||
</center>
|
||||
|
||||
<p>
|
||||
A special shout-out here goes to GPGTools for macOS.
|
||||
They implemented the update process so smoothly,
|
||||
the number of verified addresses completely exploded
|
||||
when they released their update.
|
||||
|
||||
<h4>All's good in operations</h4>
|
||||
|
||||
<p>
|
||||
There is not a lot to report operationally,
|
||||
and no news is good news in this case!
|
||||
Since launch,
|
||||
there was nearly zero downtime,
|
||||
only a single bug came up
|
||||
that briefly caused issues during upload,
|
||||
and support volume has been comfortably low.
|
||||
|
||||
<p>
|
||||
Our traffic is currently
|
||||
at about ten requests per second
|
||||
(more during the day, less on the weekend),
|
||||
and we delivered roughly 100.000 emails
|
||||
in the last month.
|
||||
No sweat.
|
||||
|
||||
<p>
|
||||
We made several small operational improvements
|
||||
including deployment of
|
||||
<a href="http://dnsviz.net/d/keys.openpgp.org/dnssec/" target="_blank">DNSSEC</a>,
|
||||
implementing some
|
||||
<a href="/about/api#rate-limiting" target="_blank">rate-limiting</a>,
|
||||
nailing down our
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">content security policy</a>
|
||||
headers,
|
||||
and enabling
|
||||
<a href="https://blog.torproject.org/whats-new-tor-0298" target="_blank">single-hop</a>
|
||||
mode on our Tor Onion Service.
|
||||
You can find a more complete list
|
||||
<a href="https://gitlab.com/hagrid-keyserver/hagrid/merge_requests?scope=all&utf8=%E2%9C%93&state=merged" target="_blank">here</a>.
|
||||
|
||||
<h4>Secure email delivery with MTA-STS</h4>
|
||||
|
||||
<p>
|
||||
One improvement that deserves special mention is
|
||||
<a href="https://www.hardenize.com/blog/mta-sts">MTA-STS</a>,
|
||||
which improves the security of outgoing emails.
|
||||
|
||||
<p>
|
||||
While HTTPS is deployed fairly universally these days,
|
||||
that sadly isn't the case for email.
|
||||
Many servers don't do encryption at all,
|
||||
or use a self-signed certificate
|
||||
instead of a proper one (e.g. from Let's Encrypt).
|
||||
But delivery failures upset customers more
|
||||
than reduced security,
|
||||
and many emails are still delivered without encryption.
|
||||
|
||||
<p>
|
||||
With MTA-STS, domain operators can indicate
|
||||
(via HTTPS)
|
||||
that their email server <em>does</em> support encryption.
|
||||
When a secure connection can't be established
|
||||
to such a server,
|
||||
message delivery will be postponed
|
||||
or eventually bounce,
|
||||
instead of proceeding insecurely.
|
||||
|
||||
<p>
|
||||
This is extremely useful for service like
|
||||
<span class="brand">keys.openpgp.org</span>.
|
||||
If encryption isn't reliable,
|
||||
attackers can intercept verification emails relatively easily.
|
||||
But for providers who have MTA-STS deployed,
|
||||
we can be sure that
|
||||
every message is delivered securely,
|
||||
and to the right server.
|
||||
|
||||
<p>
|
||||
You can <a href="https://aykevl.nl/apps/mta-sts/" target="_blank">run a check</a>
|
||||
to find out whether your email provider
|
||||
supports MTA-STS.
|
||||
If they don't,
|
||||
please drop them a message and tell them
|
||||
to step up their security game!
|
||||
|
||||
<h4>Work in progress</h4>
|
||||
|
||||
<p>
|
||||
We are working on two features:
|
||||
|
||||
<p>
|
||||
The first is <strong>localization</strong>.
|
||||
Most people do not speak English,
|
||||
but so far that is the only language we support.
|
||||
To make this service more accessible,
|
||||
we are working with the OTF's
|
||||
<a href="https://www.opentech.fund/labs/localization-lab/" target="_blank">Localization Lab</a>
|
||||
to make the website and outgoing emails
|
||||
available in several more languages.
|
||||
|
||||
<p>
|
||||
The second is to bring back
|
||||
<strong>third-party signatures</strong>.
|
||||
As <a href="/about/faq#third-party-signatures">mentioned in our FAQ</a>,
|
||||
we currently don't support these due to spam and potential for abuse.
|
||||
The idea is to require
|
||||
<a href="https://gitlab.com/openpgp-wg/rfc4880bis/merge_requests/20/diffs" target="_blank">cross-signatures</a>,
|
||||
which allow each key to choose for itself
|
||||
which signatures from other people it wants to distribute.
|
||||
Despite this extra step,
|
||||
this is fairly compatible with existing software.
|
||||
It also nicely stays out of the way of users
|
||||
who don't care about signatures.
|
||||
|
||||
<p>
|
||||
Although work is in progress for both of those features,
|
||||
neither have a planned time of release yet.
|
||||
|
||||
<p>
|
||||
Regarding the "<tt>no user ID</tt>" issue with GnuPG
|
||||
(mentioned in our
|
||||
<a href="/about/news#2019-06-12-launch-challenges">last news post</a>
|
||||
and our
|
||||
<a href="/about/faq#older-gnupg" target="_blank">FAQ</a>),
|
||||
a patch that fixes this problem is now carried by Debian,
|
||||
as well as GPGTools for macOS.
|
||||
GnuPG upstream has not merged the patch so far.
|
||||
|
||||
<p>
|
||||
That's it!
|
||||
Thanks for your interest!
|
||||
<span style="font-size: x-large;">👋</span>
|
||||
|
||||
<hr style="margin-top: 2em; margin-bottom: 2em;" />
|
||||
|
||||
<h2 id="2019-06-12-launch">
|
||||
<div style="float: right; font-size: small; line-height: 2em;">2019-06-12 📅</div>
|
||||
<a href="/about/news#2019-06-12-launch">Launching a new keyserver! 🚀</a>
|
||||
</h3>
|
||||
<a href="/about/news#2019-06-12-launch" style="color: black;">Launching a new keyserver! 🚀</a>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
From a community effort by
|
||||
@@ -24,7 +252,7 @@
|
||||
<li>Precise. Searches return only a single key, which allows for easy key discovery.</li>
|
||||
<li>Validating. Identities are only published with consent,
|
||||
while non-identity information is freely distributed.</li>
|
||||
<li>Deletable. Users can delete personal information with a simple e-mail confirmation.</li>
|
||||
<li>Deletable. Users can delete personal information with a simple email confirmation.</li>
|
||||
<li>Built on Rust, powered by <a href="https://sequoia-pgp.org" target="_blank">Sequoia PGP</a> - free and open source, running AGPLv3.</li>
|
||||
</ul>
|
||||
|
||||
@@ -64,16 +292,16 @@
|
||||
that can also be revoked at any time.
|
||||
|
||||
<p>
|
||||
If a new key is verified for some e-mail address,
|
||||
If a new key is verified for some email address,
|
||||
it will replace the previous one.
|
||||
This way,
|
||||
every e-mail address is only associated with a single key at most.
|
||||
every email address is only associated with a single key at most.
|
||||
It can also be removed from the listing
|
||||
at any time by the owner of the address.
|
||||
This is very useful for key discovery:
|
||||
if a search by e-mail address returns a key,
|
||||
if a search by email address returns a key,
|
||||
it means this is the single key
|
||||
that is currently valid for the searched e-mail address.
|
||||
that is currently valid for the searched email address.
|
||||
|
||||
<h4>Support in Enigmail and OpenKeychain</h4>
|
||||
|
||||
@@ -85,11 +313,11 @@
|
||||
<a href="https://play.google.com/store/apps/details?id=org.sufficientlysecure.keychain&hl=en">OpenKeychain</a> on Android.
|
||||
This means users of those implementations will
|
||||
benefit from the faster response times,
|
||||
and improved key discovery by e-mail address.
|
||||
and improved key discovery by email address.
|
||||
We hope that this will also give us some momentum
|
||||
to build this project into a bigger community effort.
|
||||
|
||||
<h4>Current challenges</h4>
|
||||
<h4 id="2019-06-12-launch-challenges">Current challenges</h4>
|
||||
|
||||
<p>
|
||||
Privacy-preserving techniques in keyservers are still new,
|
||||
@@ -127,6 +355,7 @@
|
||||
and an <a target="_blank" href="/about/faq#tor">Onion Service</a>!
|
||||
|
||||
<p>
|
||||
Cheers! 🍻
|
||||
|
||||
Cheers!
|
||||
<span style="font-size: x-large;">🍻</span>
|
||||
</div>
|
||||
{{/layout}}
|
||||
|
12
dist/templates/about/privacy.html.hbs
vendored
12
dist/templates/about/privacy.html.hbs
vendored
@@ -7,8 +7,8 @@
|
||||
distributes OpenPGP key data. The specific way in which data is processed
|
||||
differs by type as follows:
|
||||
<ul>
|
||||
<li><b>E-Mail Addresses</b>
|
||||
<p>E-Mail addresses contained in <abbr title="Packet Tag 13">User
|
||||
<li><b>Email Addresses</b>
|
||||
<p>Email addresses contained in <abbr title="Packet Tag 13">User
|
||||
IDs</abbr> are personally identifiable information (PII).
|
||||
Special care is taken to make sure they are used only with
|
||||
consent:
|
||||
@@ -17,9 +17,9 @@
|
||||
Publishing requires <a target="_blank"
|
||||
href="https://en.wikipedia.org/wiki/Opt-in_email#Confirmed_opt-in_(COI)_/_Double_opt-in_(DOI)">double
|
||||
opt-in</a> validation, to prove ownership of the
|
||||
e-mail address in question.
|
||||
email address in question.
|
||||
</li>
|
||||
<li>Addresses are searchable by exact E-Mail address,
|
||||
<li>Addresses are searchable by exact email address,
|
||||
but not by associated name.</li>
|
||||
<li>Enumeration of addresses is not possible.</li>
|
||||
<li>Deletion of addresses is possible via simple proof
|
||||
@@ -47,9 +47,9 @@
|
||||
</p>
|
||||
</li>
|
||||
<li><b>Other User ID data</b>
|
||||
<p>An OpenPGP key may contain personal data other than E-Mail
|
||||
<p>An OpenPGP key may contain personal data other than email
|
||||
addresses, such as <abbr title="Packet Tag 13">User IDs</abbr>
|
||||
that do not contain E-Mail addresses, or <abbr
|
||||
that do not contain email addresses, or <abbr
|
||||
title="Packet Tag 17">image attributes</abbr>. This data is stripped
|
||||
during upload and never stored, processed, or distributed in
|
||||
any way.
|
||||
|
11
dist/templates/about/stats.html.hbs
vendored
11
dist/templates/about/stats.html.hbs
vendored
@@ -2,18 +2,18 @@
|
||||
<div class="about">
|
||||
<center><h2><a href="/about">About</a> | <a href="/about/news">News</a> | <a href="/about/usage">Usage</a> | <a href="/about/faq">FAQ</a> | Stats | <a href="/about/privacy">Privacy</a></h2></center>
|
||||
|
||||
<h3>Verified e-mail addresses</h3>
|
||||
<h3>Verified email addresses</h3>
|
||||
|
||||
<p>
|
||||
A simple statistic of the total number of e-mail addresses that are currently verified. 📈
|
||||
A simple statistic of the total number of email addresses that are currently verified. 📈
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<center><img src="/about/stats/week.png" /></center>
|
||||
</p>
|
||||
<p>
|
||||
<center><img src="/about/stats/month.png" /></center>
|
||||
</p>
|
||||
<p>
|
||||
<center><img src="/about/stats/year.png" /></center>
|
||||
</p>
|
||||
|
||||
<h3>Load Average</h3>
|
||||
|
||||
@@ -31,4 +31,3 @@
|
||||
</p>
|
||||
</div>
|
||||
{{/layout}}
|
||||
|
||||
|
106
dist/templates/about/usage.html.hbs
vendored
106
dist/templates/about/usage.html.hbs
vendored
@@ -10,35 +10,67 @@
|
||||
write to us and we'll try to add it.
|
||||
</p>
|
||||
|
||||
<h2 style="margin-left: 3%;"><img src="/assets/img/enigmail.svg" style="display: inline; height: 2em; vertical-align: -30%; padding-right: 10px;" /> Enigmail</h2>
|
||||
<h2>
|
||||
<div><img src="/assets/img/enigmail.svg"></div>
|
||||
Enigmail
|
||||
</h2>
|
||||
<p>
|
||||
Enigmail will support <span class="brand">keys.openpgp.org</span> out
|
||||
of the box in an upcoming version. Stay tuned!
|
||||
<a href="https://enigmail.net" target="_blank">Enigmail</a> for Thunderbird
|
||||
uses <span class="brand">keys.openpgp.org</span> by default since
|
||||
version 2.0.12.
|
||||
</p>
|
||||
<p>Full support is available since Enigmail 2.1
|
||||
(for <a href="https://www.thunderbird.net/en-US/thunderbird/68.0beta/releasenotes/" target="_blank">Thunderbird 68</a> or newer):
|
||||
<ul>
|
||||
<li>Keys will be kept up to date automatically.</li>
|
||||
<li>During creation, you can optionally upload and verify your key.</li>
|
||||
<li>During key creation, you can upload and verify your key.</li>
|
||||
<li>Keys can be discovered by email address.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h2 style="margin-left: 3%;"><img src="/assets/img/openkeychain.svg" style="display: inline; height: 2em; vertical-align: -30%; padding-right: 10px;" /> OpenKeychain</h2>
|
||||
<h2>
|
||||
<div><img src="/assets/img/gpgtools.png"></div>
|
||||
GPG Suite
|
||||
</h2>
|
||||
<p>
|
||||
OpenKeychain will support <span class="brand">keys.openpgp.org</span>
|
||||
out of the box in an upcoming version. Stay tuned!
|
||||
<a href="https://gpgtools.org/">GPG Suite</a> for macOS
|
||||
uses <span class="brand">keys.openpgp.org</span> by default
|
||||
since August 2019.
|
||||
</p>
|
||||
|
||||
<h2>
|
||||
<div><img src="/assets/img/openkeychain.svg"></div>
|
||||
OpenKeychain
|
||||
</h2>
|
||||
<p>
|
||||
<a href="https://www.openkeychain.org/">OpenKeychain</a> for Android
|
||||
uses <span class="brand">keys.openpgp.org</span> by default
|
||||
since July 2019.
|
||||
<ul>
|
||||
<li>Keys will be kept up to date automatically.</li>
|
||||
<li>During creation, you can optionally upload and verify your key.</li>
|
||||
<li>Keys can be discovered by email address.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<h2 style="margin-left: 3%;"><img src="/assets/img/gnupg.svg" style="display: inline; height: 2em; vertical-align: -30%; padding-right: 10px;" /> GnuPG</h2>
|
||||
<p>
|
||||
Note that there is no built-in support for upload and email address verification so far.
|
||||
</p>
|
||||
|
||||
To configure GnuPG to use <span class="brand">keys.openpgp.org</span>
|
||||
as keyserver, add this line to your <tt>gpg.conf</tt> file:
|
||||
<blockquote>
|
||||
keyserver hkps://keys.openpgp.org
|
||||
</blockquote>
|
||||
<h2>
|
||||
<div><img src="/assets/img/gnupg.svg" /></div>
|
||||
GnuPG
|
||||
</h2>
|
||||
|
||||
<h3>Retrieving keys</h3>
|
||||
<p>
|
||||
To configure <a href="https://gnupg.org">GnuPG</a>
|
||||
to use <span class="brand">keys.openpgp.org</span> as keyserver,
|
||||
add this line to your <tt>gpg.conf</tt> file:
|
||||
<blockquote>
|
||||
keyserver hkps://keys.openpgp.org
|
||||
</blockquote>
|
||||
</p>
|
||||
|
||||
<h4 id="gnupg-retrieve"><a href="#gnupg-retrieve">Retrieving keys</a></h4>
|
||||
<ul>
|
||||
<li>
|
||||
To locate the key of a user, by email address:
|
||||
@@ -46,18 +78,14 @@
|
||||
</li>
|
||||
<li>To refresh all your keys (e.g. new revocation certificates and subkeys):
|
||||
<blockquote>gpg --refresh-keys</blockquote>
|
||||
<b>Note:</b> If you see errors like the following,
|
||||
see <a href="/about/faq#older-gnupg">our notes</a> on compatibility
|
||||
with older versions of GnuPG.
|
||||
<blockquote>gpg: key A2604867523C7ED8: no user ID</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="gnupg-upload">Uploading your key</h3>
|
||||
<h4 id="gnupg-upload"><a href="#gnupg-upload">Uploading your key</a></h4>
|
||||
<p>
|
||||
Keys can be uploaded with GnuPG's <tt>--send-keys</tt> command, but
|
||||
identity information can't be verified that way to make the key
|
||||
searchable by e-mail address (<a href="/about">what's that?</a>).
|
||||
searchable by email address (<a href="/about">what does this mean?</a>).
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
@@ -65,23 +93,45 @@
|
||||
You can try this shortcut for uploading your key, which outputs
|
||||
a direct link to the verification page:
|
||||
<blockquote>
|
||||
gpg --export your_address@example.net | curl -T - {{base_uri}}
|
||||
gpg --export your_address@example.net | curl -T - {{ base_uri }}
|
||||
</blockquote>
|
||||
</li>
|
||||
<li>
|
||||
Alternatively, you can export them to a file and enter that into the
|
||||
<a href="/upload" target="_blank">upload</a> page:
|
||||
|
||||
Alternatively, you can export them to a file
|
||||
and select that file in the <a href="/upload" target="_blank">upload</a> page:
|
||||
<blockquote>
|
||||
gpg --export your_address@example.net > my_key.pub
|
||||
</blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Usage via Tor</h3>
|
||||
<h4 id="gnupg-troubleshooting"><a href="#gnupg-troubleshooting">Troubleshooting</a></h4>
|
||||
<ul>
|
||||
<li>
|
||||
Some old <tt>~/gnupg/dirmngr.conf</tt> files contain a line like this:
|
||||
<blockquote>
|
||||
hkp-cacert ~/.gnupg/sks-keyservers.netCA.pem
|
||||
</blockquote>
|
||||
<p>
|
||||
This configuration is no longer necessary,
|
||||
but prevents regular certificates from working.
|
||||
It is recommended to simply remove this line from the configuration.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
While refreshing keys, you may see errors like the following:
|
||||
<blockquote>gpg: key A2604867523C7ED8: no user ID</blockquote>
|
||||
This is a <a href="https://dev.gnupg.org/T4393" target="_blank">known problem in GnuPG</a>.
|
||||
We are working with the GnuPG team to resolve this issue.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4 id="gnupg-tor"><a href="#gnupg-tor">Usage via Tor</a></h4>
|
||||
<p>
|
||||
For users who want to be extra-careful,
|
||||
For users who want to be extra careful,
|
||||
<span class="brand">keys.openpgp.org</span> can be reached anonymously as an
|
||||
<a href="https://en.wikipedia.org/wiki/Tor_(anonymity_network)#Onion_services" target="_blank">onion service</a>.
|
||||
<a href="https://support.torproject.org/onionservices/#onionservices-2" target="_blank">onion service</a>.
|
||||
If you have
|
||||
<a href="https://www.torproject.org/" target="_blank">Tor</a>
|
||||
installed, use the following configuration:
|
||||
@@ -101,7 +151,7 @@
|
||||
|
||||
<p>
|
||||
Missing a guide for your favorite implementation? This site is
|
||||
a work-in-progress, and we are looking to improve. Drop us a line at
|
||||
a work-in-progress, and we are looking to improve it. Drop us a line at
|
||||
<span class="email">support at keys dot openpgp dot org</span> if you
|
||||
want to help out!
|
||||
</p>
|
||||
|
25
dist/templates/atom.xml.hbs
vendored
Normal file
25
dist/templates/atom.xml.hbs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>keys.openpgp.org</title>
|
||||
<link href="{{ base_uri }}/atom.xml" rel="self"/>
|
||||
<id>urn:uuid:8e783366-73b1-460e-83d3-42f01046646d</id>
|
||||
<updated>2019-11-12T12:00:00Z</updated>
|
||||
<entry>
|
||||
<title>Celebrating 100.000 verified addresses! 📈</title>
|
||||
<link href="{{ base_uri }}/about/news#2019-11-12-celebrating-100k" />
|
||||
<updated>2019-11-12T12:00:00Z</updated>
|
||||
<id>urn:uuid:5b69781f-5aa4-4276-8d9e-6a71c896cb65</id>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Launching a new keyserver! 🚀</title>
|
||||
<link href="{{ base_uri }}/about/news#2019-06-12-launch" />
|
||||
<updated>2019-06-12T12:00:00Z</updated>
|
||||
<id>urn:uuid:a071a6dc-f8ea-43de-b853-bd6d8bbe063f</id>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Three months after launch ✨</title>
|
||||
<link href="{{ base_uri }}/about/news#2019-09-12-three-months-later" />
|
||||
<updated>2019-09-12T12:00:00Z</updated>
|
||||
<id>urn:uuid:1bd5412a-f480-4c3f-a72d-c9b7a849f544</id>
|
||||
</entry>
|
||||
</feed>
|
26
dist/templates/email/manage-html.hbs
vendored
26
dist/templates/email/manage-html.hbs
vendored
@@ -1,26 +0,0 @@
|
||||
|
||||
<!doctype html>
|
||||
<html lang=en>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Manage your key on {{domain}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Hi,
|
||||
<p>
|
||||
this is an automated message from <a rel="nofollow" href="{{base_uri}}" style="text-decoration:none; color: #333"><tt>{{domain}}</tt></a>. If you didn't
|
||||
request this message, please ignore it.
|
||||
<p>
|
||||
OpenPGP key: <tt>{{primary_fp}}</tt>
|
||||
<p>
|
||||
To manage and delete listed addresses on this key, please follow
|
||||
the link below:
|
||||
<p>
|
||||
<a href="{{uri}}">{{uri}}</a>
|
||||
<p>
|
||||
You can find more info at <a href="{{base_uri}}/about">{{domain}}/about</a>.
|
||||
<p>
|
||||
Greetings from the <a rel="nofollow" href="{{base_uri}}" style="text-decoration:none; color: #333"><tt>keys.openpgp.org</tt></a> team
|
||||
</body>
|
||||
</html>
|
16
dist/templates/email/manage-txt.hbs
vendored
16
dist/templates/email/manage-txt.hbs
vendored
@@ -1,16 +0,0 @@
|
||||
Hi,
|
||||
|
||||
this is an automated message from {{domain}}. If you didn't
|
||||
request this message, please ignore it.
|
||||
|
||||
OpenPGP key: {{primary_fp}}
|
||||
|
||||
To manage and delete listed addresses on this key, please follow
|
||||
the link below:
|
||||
|
||||
{{uri}}
|
||||
|
||||
You can find more info at {{base_uri}}/about
|
||||
|
||||
Greetings from the keys.openpgp.org team
|
||||
|
26
dist/templates/email/publish-html.hbs
vendored
26
dist/templates/email/publish-html.hbs
vendored
@@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang=en>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Verify {{userid}} for your key on {{domain}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Hi,
|
||||
<p>
|
||||
this is an automated message from <a rel="nofollow" href="{{base_uri}}" style="text-decoration:none; color: #333"><tt>{{domain}}</tt></a>. If you didn't
|
||||
request this message, please ignore it.
|
||||
<p>
|
||||
OpenPGP key: <tt>{{primary_fp}}</tt>
|
||||
<p>
|
||||
To let others find this key from your email address
|
||||
"<a rel="nofollow" href="#" style="text-decoration:none; color: #333">{{userid}}</a>",
|
||||
please click the link below:
|
||||
<p>
|
||||
<a href="{{uri}}">{{uri}}</a>
|
||||
<p>
|
||||
You can find more info at <a href="{{base_uri}}/about">{{domain}}/about</a>.
|
||||
<p>
|
||||
Greetings from the <a rel="nofollow" href="{{base_uri}}" style="text-decoration:none; color: #333"><tt>keys.openpgp.org</tt></a> team
|
||||
</body>
|
||||
</html>
|
15
dist/templates/email/publish-txt.hbs
vendored
15
dist/templates/email/publish-txt.hbs
vendored
@@ -1,15 +0,0 @@
|
||||
Hi,
|
||||
|
||||
this is an automated message from {{domain}}. If you didn't
|
||||
request this message, please ignore it.
|
||||
|
||||
OpenPGP key: {{primary_fp}}
|
||||
|
||||
To let others find this key from your email address "{{userid}}",
|
||||
please follow the link below:
|
||||
|
||||
{{uri}}
|
||||
|
||||
You can find more info at {{base_uri}}/about
|
||||
|
||||
Greetings from the keys.openpgp.org team
|
5
dist/templates/errors/429-rate-limit-web.html.hbs
vendored
Normal file
5
dist/templates/errors/429-rate-limit-web.html.hbs
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{{#> layout }}
|
||||
<h2>Too many requests!</h2>
|
||||
|
||||
<tt>Slow down a little</tt>
|
||||
{{/layout}}
|
30
dist/templates/found.html.hbs
vendored
30
dist/templates/found.html.hbs
vendored
@@ -1,20 +1,20 @@
|
||||
{{#> layout }}
|
||||
<div class="ui">
|
||||
<p>
|
||||
We found an entry for <span class="email">{{ query }}</span>:
|
||||
</p>
|
||||
{{#with page}}
|
||||
<div class="ui">
|
||||
<p>
|
||||
{{ text "We found an entry for <span class=\"email\">{{ query }}</span>:" rerender }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{base_uri}}/vks/v1/by-fingerprint/{{ fpr }}">{{base_uri}}/vks/v1/by-fingerprint/{{ fpr }}</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ ../base_uri }}/vks/v1/by-fingerprint/{{ fpr }}">{{ ../base_uri }}/vks/v1/by-fingerprint/{{ fpr }}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Hint:</strong> It's more convenient to use
|
||||
<span class="brand">keys.openpgp.org</span> from your OpenPGP software.<br />
|
||||
Take a look at our <a href="/about/usage">usage guide</a> for details.
|
||||
</p>
|
||||
<div class="debug_link" style="color: transparent;">
|
||||
<a href="{{base_uri}}/debug?q={{ fpr }}">debug info</a>
|
||||
<p>
|
||||
{{text "<strong>Hint:</strong> It's more convenient to use <span class=\"brand\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at our <a href=\"/about/usage\">usage guide</a> for details." }}
|
||||
</p>
|
||||
<div class="debug_link" style="color: transparent;">
|
||||
<a href="{{ ../base_uri }}/debug?q={{ fpr }}">{{ text "debug info" }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
25
dist/templates/index.html.hbs
vendored
25
dist/templates/index.html.hbs
vendored
@@ -1,14 +1,31 @@
|
||||
{{#> layout }}
|
||||
{{> search-form}}
|
||||
<p>You can also <a href="/upload">upload</a> or <a href="/manage">manage</a> your key.</p>
|
||||
{{#with page}}
|
||||
|
||||
{{#with error}}
|
||||
<p><strong>Error</strong>: {{ this }}</p>
|
||||
{{/with}}
|
||||
|
||||
<form action="/search" method="GET">
|
||||
<div class="search">
|
||||
<input type="text" class="searchTerm" id="search" name="q" autofocus placeholder="{{ text "Search by Email Address / Key ID / Fingerprint" }}">
|
||||
<button type="submit" class="searchButton button">
|
||||
<img src="/assets/search.svg" style="width: 1em; padding-bottom: 4px;"> {{ text "Search" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
Find out more <a href="/about">about this service</a>.
|
||||
{{ text "You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</a> your key." }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ text "Find out more <a href=\"/about\">about this service</a>." }}
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
<strong>News:</strong> <a href="/about/news#2019-06-12-launch">Launching a new keyserver! 🚀</a> (2019-06-12)
|
||||
<strong>{{ text "News:" }}</strong> {{ text "<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 verified addresses! 📈</a> (2019-11-12)" }}
|
||||
</p>
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
16
dist/templates/layout.html.hbs
vendored
16
dist/templates/layout.html.hbs
vendored
@@ -1,27 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="{{lang}}">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=17" type="text/css"/>
|
||||
<link rel="stylesheet" href="/assets/site.css?v=19" type="text/css"/>
|
||||
<link rel="alternate" href="/atom.xml" type="application/atom+xml" title="keys.openpgp.org newsfeed" />
|
||||
<title>keys.openpgp.org</title>
|
||||
</head>
|
||||
<body lang="en">
|
||||
<body lang="{{lang}}">
|
||||
<div class="card">
|
||||
<h1><a class="brand" href="/">keys.openpgp.org</a></h1>
|
||||
{{#with error }}
|
||||
<p><strong>Error</strong>: {{ this }}</p>
|
||||
{{/with}}
|
||||
{{> @partial-block }}
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
<div class="attribution">
|
||||
<p>
|
||||
<a href="https://gitlab.com/hagrid-keyserver/hagrid/">Hagrid</a>
|
||||
v{{ version }} built from
|
||||
{{ text "v{{ version }} built from" rerender }}
|
||||
<a href="https://gitlab.com/hagrid-keyserver/hagrid/commit/{{ commit }}">{{ commit }}</a>
|
||||
</p>
|
||||
<p>Powered by <a href="https://sequoia-pgp.org">Sequoia-PGP</a></p>
|
||||
<p>Background image retrieved from <a href="https://www.toptal.com/designers/subtlepatterns/subtle-grey/">Subtle Patterns</a> under CC BY-SA 3.0</p>
|
||||
<p>{{ text "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>" }}</p>
|
||||
<p>{{ text "Background image retrieved from <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0" }}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
3
dist/templates/maintenance.html.hbs
vendored
3
dist/templates/maintenance.html.hbs
vendored
@@ -1,8 +1,7 @@
|
||||
{{#> layout }}
|
||||
<h2>Maintenance Mode</h2>
|
||||
<h2>{{ text "Maintenance Mode" }}</h2>
|
||||
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
|
||||
{{/layout}}
|
||||
|
17
dist/templates/manage/manage.html.hbs
vendored
17
dist/templates/manage/manage.html.hbs
vendored
@@ -1,18 +1,25 @@
|
||||
{{#> layout }}
|
||||
<center><h2>Manage your key</h2></center>
|
||||
{{#with page}}
|
||||
|
||||
{{#with error }}
|
||||
<p><strong>Error</strong>: {{ this }}</p>
|
||||
{{/with}}
|
||||
|
||||
<center><h2>{{ text "Manage your key" }}</h2></center>
|
||||
|
||||
<form action="/manage" method="POST">
|
||||
<div class="manage">
|
||||
<input type="text" name="search_term" class="manageEmail" autofocus
|
||||
placeholder="Enter any verified e-mail address of your key">
|
||||
placeholder="{{ text "Enter any verified email address for your key" }}">
|
||||
<button type="submit" class="manageButton button">
|
||||
Send link
|
||||
{{ text "Send link" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
We will send you an e-mail with a link you can use to remove any of your
|
||||
e-mail addresses from search.
|
||||
{{ text "We will send you an email with a link you can use to remove any of your email addresses from search." }}
|
||||
</p>
|
||||
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
16
dist/templates/manage/manage_key.html.hbs
vendored
16
dist/templates/manage/manage_key.html.hbs
vendored
@@ -1,11 +1,12 @@
|
||||
{{#> layout }}
|
||||
{{#with page}}
|
||||
<p>
|
||||
Managing the key <span class="fingerprint"><a href="{{key_link}}" target="_blank">{{key_fpr}}</a></span>.
|
||||
{{ text "Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>." rerender }}
|
||||
</p>
|
||||
|
||||
{{#if uid_status}}
|
||||
<p style="padding-top: 1em;">
|
||||
Your key is published with the following identity information:
|
||||
{{ text "Your key is published with the following identity information:" }}
|
||||
</p>
|
||||
|
||||
{{#each uid_status}}
|
||||
@@ -14,7 +15,7 @@
|
||||
<form action="/manage/unpublish" method="post">
|
||||
<input type="hidden" name="token" value="{{../token}}" />
|
||||
<input type="hidden" name="address" value="{{address}}" />
|
||||
<input type="submit" class="link" value="Delete">
|
||||
<input type="submit" class="link" value="{{ text "Delete" }}">
|
||||
</form>
|
||||
</div>
|
||||
<p>
|
||||
@@ -24,19 +25,18 @@
|
||||
{{/each}}
|
||||
|
||||
<p style="line-height: 1.8em;">
|
||||
Clicking "delete" on any address will remove it from this key. It will no longer appear in a search.<br />
|
||||
To add another address, <a href="/upload">upload</a> the key again.
|
||||
{{ text "Clicking \"delete\" on any address will remove it from this key. It will no longer appear in a search.<br /> To add another address, <a href=\"/upload\">upload</a> the key again." }}
|
||||
</p>
|
||||
|
||||
{{else}}
|
||||
<p style="padding-top: 1em;">
|
||||
Your key is published as only non-identity information.
|
||||
(<a href="/about" target="_blank">what does this mean?</a>)
|
||||
{{ text "Your key is published as only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)" }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To add an address, <a href="/upload">upload</a> the key again.
|
||||
{{ text "To add an address, <a href=\"/upload\">upload</a> the key again." }}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
{{#> layout }}
|
||||
{{#with page}}
|
||||
<p>
|
||||
We have sent an email with further instructions to <span class="email">{{address}}</span>.
|
||||
{{ text "We have sent an email with further instructions to <span class=\"email\">{{ address }}</span>." rerender }}
|
||||
</p>
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
10
dist/templates/search-form.html.hbs
vendored
10
dist/templates/search-form.html.hbs
vendored
@@ -1,10 +0,0 @@
|
||||
<div class="row">
|
||||
<form action="/search" method=GET>
|
||||
<div class="search">
|
||||
<input type="text" class="searchTerm" id="search" name="q" autofocus placeholder="Search by Email Address / Key ID / Fingerprint">
|
||||
<button type="submit" class="searchButton button">
|
||||
<img src="/assets/search.svg" style="width: 1em; padding-bottom: 4px;"> Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
18
dist/templates/upload/publish-result.html.hbs
vendored
18
dist/templates/upload/publish-result.html.hbs
vendored
@@ -1,20 +1,10 @@
|
||||
{{#> layout}}
|
||||
<div class="row">
|
||||
{{#if verified }}
|
||||
{{#with page}}
|
||||
<div id="verification-result">
|
||||
<p>
|
||||
Your key
|
||||
<span class="fingerprint">{{key_fpr}}</span>
|
||||
is now published
|
||||
for the identity <a href="{{userid_link}}" target="_blank"><span class="email">{{ userid }}</span></a>.
|
||||
{{ text "Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</span></a>." rerender }}
|
||||
</p>
|
||||
{{else}}
|
||||
<p>
|
||||
Verification failed! Perhaps the link you used was expired?
|
||||
</p>
|
||||
<p>
|
||||
You can <a href="/upload">try uploading again</a>.
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
||||
|
@@ -1,15 +1,17 @@
|
||||
{{#> layout }}
|
||||
{{#with page}}
|
||||
<p>
|
||||
Your keys have been successfully uploaded:
|
||||
{{ text "Your keys have been successfully uploaded:" }}
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
{{#each keys}}
|
||||
<li><span class="fingerprint"><a href="{{key_link}}" target="_blank">{{key_fpr}}</a></span></li>
|
||||
<li><span class="fingerprint"><a href="{{ key_link }}" target="_blank">{{ key_fpr }}</a></span></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<strong>Note:</strong> To make keys searchable by address, you must upload them individually.
|
||||
{{ text "<strong>Note:</strong> To make keys searchable by email address, you must upload them individually." }}
|
||||
</p>
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
50
dist/templates/upload/upload-ok.html.hbs
vendored
50
dist/templates/upload/upload-ok.html.hbs
vendored
@@ -1,75 +1,67 @@
|
||||
{{#> layout }}
|
||||
{{#with page}}
|
||||
|
||||
<p>
|
||||
You uploaded the key <span class="fingerprint"><a href="{{key_link}}" target="_blank">{{key_fpr}}</a></span>.
|
||||
{{ text "You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>." rerender }}
|
||||
</p>
|
||||
|
||||
{{#if is_revoked}}
|
||||
<p>
|
||||
<strong>This key is revoked.</strong>
|
||||
It is published without identity information
|
||||
(<a href="/about" target="_blank">what does this mean?</a>),
|
||||
and can't be made available for search
|
||||
by e-mail address.
|
||||
<strong>{{ text "This key is revoked." }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{{ text "It is published without identity information and can't be made available for search by email address. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)" }}
|
||||
</p>
|
||||
{{else}}
|
||||
{{#if email_published}}
|
||||
<p style="padding-top: 1em;">
|
||||
This key is now published with the following identity information (<a href="/about" target="_blank">what does this mean?</a>):
|
||||
{{ text "This key is now published with the following identity information (<a href=\"/about\" target=\"_blank\">what does this mean?</a>):" }}
|
||||
</p>
|
||||
{{#each email_published}}
|
||||
<div class="publishedUid">
|
||||
<div>Published</div>
|
||||
<div>{{ text "Published" }}</div>
|
||||
<p><span class="email">{{this}}</span></p>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<p style="padding-top: 1em;">
|
||||
This key is now published with only non-identity information (<a href="/about" target="_blank">what does this mean?</a>)
|
||||
{{ text "This key is now published with only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)" }}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if email_unpublished}}
|
||||
<p style="padding-top: 1em;">
|
||||
To make the key available for search by e-mail address, you can verify it belongs to you:
|
||||
{{ text "To make the key available for search by email address, you can verify it belongs to you:" }}
|
||||
</p>
|
||||
{{#each email_unpublished}}
|
||||
<div class="publishedUid">
|
||||
<div>
|
||||
{{#if requested}}
|
||||
Verification Pending
|
||||
{{ text "Verification Pending" }}
|
||||
{{else}}
|
||||
<form action="/upload/request-verify" method="post">
|
||||
<input type="hidden" name="token" value="{{../token}}" />
|
||||
<input type="hidden" name="address" value="{{address}}" />
|
||||
<input type="submit" class="link" value="Send Verification Mail">
|
||||
<input type="submit" class="link" value="{{ text "Send Verification Email" }}">
|
||||
</form>
|
||||
{{/if}}
|
||||
</div>
|
||||
<p><span class="email">{{address}}</span></p>
|
||||
<p><span class="email">{{ address }}</span></p>
|
||||
</div>
|
||||
{{/each}}
|
||||
<p>
|
||||
<strong>Note:</strong> Some providers delay e-mails for up to 15 minutes
|
||||
to prevent spam. Please be patient.
|
||||
{{ text "<strong>Note:</strong> Some providers delay emails for up to 15 minutes to prevent spam. Please be patient." }}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if count_unparsed}}
|
||||
{{#if count_unparsed_one}}
|
||||
<p style="padding-top: 1em;">
|
||||
This key contains one identity that could not be parsed as an email
|
||||
address.<br />
|
||||
This identity can't be published
|
||||
on <span class="brand">keys.openpgp.org</span>.
|
||||
(<a href="/about/faq#non-email-uids" target="_blank">why?</a>)
|
||||
{{ text "This key contains one identity that could not be parsed as an email address.<br /> This identity can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)" }}
|
||||
</p>
|
||||
{{else}}
|
||||
<p style="padding-top: 1em;">
|
||||
This key contains {{count_unparsed}} identities that could not be parsed
|
||||
as an email address.<br />
|
||||
These identities can't be published
|
||||
on <span class="brand">keys.openpgp.org</span>.
|
||||
(<a href="/about/faq#non-email-uids" target="_blank">why?</a>)
|
||||
{{ text "This key contains {{ count_unparsed }} identities that could not be parsed as an email address.<br /> These identities can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)" rerender }}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
@@ -77,18 +69,16 @@
|
||||
{{#if count_revoked}}
|
||||
{{#if count_revoked_one}}
|
||||
<p style="padding-top: 1em;">
|
||||
This key contains one revoked identity, which is not published.
|
||||
(<a href="/about/faq#revoked-uids" target="_blank">Why?</a>)
|
||||
{{ text "This key contains one revoked identity, which is not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)" rerender }}
|
||||
</p>
|
||||
{{else}}
|
||||
<p style="padding-top: 1em;">
|
||||
This key contains {{count_revoked}} revoked identities, which are not
|
||||
published.
|
||||
(<a href="/about/faq#revoked-uids" target="_blank">Why?</a>)
|
||||
{{ text "This key contains {{ count_revoked }} revoked identities, which are not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)" rerender }}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
31
dist/templates/upload/upload.html.hbs
vendored
31
dist/templates/upload/upload.html.hbs
vendored
@@ -1,35 +1,26 @@
|
||||
{{#> layout }}
|
||||
{{#with page}}
|
||||
<div class="ui">
|
||||
<center><h2>Upload your key</h2></center>
|
||||
<center><h2>{{ text "Upload your key" }}</h2></center>
|
||||
|
||||
{{#with error }}
|
||||
<p><strong>Error</strong>: {{ this }}</p>
|
||||
{{/with}}
|
||||
|
||||
<form action="/upload/submit" method="POST" enctype="multipart/form-data">
|
||||
<div class="upload">
|
||||
<input type="file" id="keytext" name="keytext" autofocus class="fileUpload" placeholder="Your public key"/>
|
||||
<input type="file" id="keytext" name="keytext" autofocus class="fileUpload" placeholder="{{ text "Your public key" }}"/>
|
||||
<button type="submit" class="uploadButton button">
|
||||
<img src="/assets/upload.svg" style="width: 1em;"> Upload
|
||||
<img src="/assets/upload.svg" style="width: 1em;"> {{ text "Upload" }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
Need more info? Check our <a target="_blank" href="/about">intro</a> and <a target="_blank" href="/about/usage">usage guide</a>!
|
||||
{{ text "Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and <a target=\"_blank\" href=\"/about/usage\">usage guide</a>." }}
|
||||
</p>
|
||||
|
||||
<script>
|
||||
var body = document.getElementsByTagName("body")[0];
|
||||
|
||||
body.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, false);
|
||||
|
||||
body.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.dataTransfer) {
|
||||
document.getElementById('keytext').files = e.dataTransfer.files;
|
||||
}
|
||||
}, false);
|
||||
</script>
|
||||
<script src="/assets/js/upload.js" async></script>
|
||||
</div>
|
||||
{{/with}}
|
||||
{{/layout}}
|
||||
|
17
dist/templates/upload/verification-form.html.hbs
vendored
Normal file
17
dist/templates/upload/verification-form.html.hbs
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{{#> layout}}
|
||||
{{#with page}}
|
||||
<div id="container">
|
||||
<p>
|
||||
{{ text "Verifying your email address…" }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<form method="POST" action="/verify/{{token}}" id="postform">
|
||||
{{ text "If the process doesn't complete after a few seconds, please <input type=\"submit\" class=\"textbutton\" value=\"click here\" />." }}
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/upload-verify.js" type="text/javascript"></script>
|
||||
{{/with}}
|
||||
{{/layout}}
|
@@ -5,6 +5,8 @@
|
||||
client_max_body_size 1m;
|
||||
client_body_buffer_size 128k;
|
||||
|
||||
limit_req_status 429;
|
||||
|
||||
# Change all HTTP 502 errors into 500, to avoid being "marked as dead" by GnuPG
|
||||
# if we ever get a spurious 502 (e.g. during a restart of hagrid).
|
||||
# See https://gitlab.com/sequoia-pgp/hagrid/issues/94
|
||||
@@ -28,19 +30,10 @@ location /vks/v1/request-verify {
|
||||
}
|
||||
|
||||
location /vks/v1/ {
|
||||
location ~ ^/vks/v1/by-email/([^/][^/])([^/][^/])([^/]*)$ {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
# we have some trouble with uri encoding here. just route through
|
||||
# hagrid, for now.
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
# error_page 404 /errors/404-by-email.htm;
|
||||
# default_type application/pgp-keys;
|
||||
# add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
|
||||
# try_files /keys/links/by-email/$1/$2/$3 =404;
|
||||
}
|
||||
|
||||
location ~ ^/vks/v1/by-fingerprint/(?:0x)?([^/][^/])([^/][^/])(..*)$ {
|
||||
error_page 404 /errors/404-by-fpr.htm;
|
||||
limit_req zone=search_fpr_keyid burst=30;
|
||||
|
||||
error_page 404 /errors-static/404-by-fpr.htm;
|
||||
default_type application/pgp-keys;
|
||||
add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
@@ -48,55 +41,131 @@ location /vks/v1/ {
|
||||
}
|
||||
|
||||
location ~ ^/vks/v1/by-keyid/(?:0x)?([^/][^/])([^/][^/])(.*)$ {
|
||||
error_page 404 /errors/404-by-keyid.htm;
|
||||
limit_req zone=search_fpr_keyid burst=30;
|
||||
error_page 429 /errors-static/429-rate-limit-vks-fpr.htm;
|
||||
|
||||
error_page 404 /errors-static/404-by-keyid.htm;
|
||||
default_type application/pgp-keys;
|
||||
add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
try_files /keys/links/by-keyid/$1/$2/$3 =404;
|
||||
}
|
||||
|
||||
location /vks/v1/by-email/ {
|
||||
limit_req zone=search_email burst=50 nodelay;
|
||||
error_page 429 /errors-static/429-rate-limit-vks-email.htm;
|
||||
|
||||
set $args "";
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
|
||||
# we have some trouble with uri encoding here. just route through
|
||||
# hagrid, for now.
|
||||
# error_page 404 /errors-static/404-by-email.htm;
|
||||
# default_type application/pgp-keys;
|
||||
# add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
|
||||
# try_files /keys/links/by-email/$1/$2/$3 =404;
|
||||
}
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
error_page 400 /errors/400-vks-invalid.htm;
|
||||
error_page 400 /errors-static/400-vks-invalid.htm;
|
||||
return 400;
|
||||
}
|
||||
|
||||
# Common HKP requests.
|
||||
location /pks/lookup {
|
||||
# rewrite this to a path we can match on (right below)
|
||||
if ($args ~ "op=.*search=") {
|
||||
# urldecode the search parameter
|
||||
set_by_lua $arg_search_decoded "return ngx.arg[1]:gsub(\"%%(%x%x)\", function(x) return string.char(tonumber(x, 16)) end)" $arg_search;
|
||||
# if the search argument contains angle brackets, strip everything around them
|
||||
set_by_lua $arg_search_decoded "return ngx.arg[1]:gsub(\"^.*<([^>]-)>.*$\", \"%1\")" $arg_search_decoded;
|
||||
rewrite . /pks/internal/$arg_op/$arg_search_decoded last;
|
||||
}
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
error_page 400 /errors-static/400-pks-invalid.htm;
|
||||
return 400;
|
||||
}
|
||||
|
||||
location /pks/internal {
|
||||
internal;
|
||||
|
||||
# search by key id
|
||||
# sq keyserver get <KEYID>, gpg --receive-keys <KEYID>
|
||||
if ($args ~ "^op=get&options=mr&search=(?:0x)?([a-fA-F0-9]{16})$") {
|
||||
location ~ "^/pks/internal/get/(?:0x)?([a-fA-F0-9]{16})$" {
|
||||
set_by_lua $keyid "return ngx.arg[1]:upper()" $1;
|
||||
set $args "";
|
||||
rewrite . /vks/v1/by-keyid/$keyid last;
|
||||
}
|
||||
|
||||
# search by fpr
|
||||
# gpg --receive-keys <FINGERPRINT>
|
||||
if ($args ~ "^op=get&options=mr&search=(?:0x)?([a-fA-F0-9]{40})$") {
|
||||
location ~ "^/pks/internal/get/(?:0x)?([a-fA-F0-9]{40})$" {
|
||||
set_by_lua $fingerprint "return ngx.arg[1]:upper()" $1;
|
||||
set $args "";
|
||||
rewrite . /vks/v1/by-fingerprint/$fingerprint last;
|
||||
}
|
||||
|
||||
# search by email
|
||||
# gpg --locate-key <EMAIL>
|
||||
if ($request_uri ~ "^/pks/lookup\?op=get&options=mr&search=([^&]{3,}%40[^&]+)") {
|
||||
location ~ "^/pks/internal/get/(.+(?:%40|@).+)$" {
|
||||
set_by_lua $email "return ngx.arg[1]:lower()" $1;
|
||||
set $args "";
|
||||
rewrite . /vks/v1/by-email/$email last;
|
||||
}
|
||||
|
||||
# gpg --search '<address@example.org>'
|
||||
# strip angle brackets - we don't need them, but they cause issues
|
||||
# with the Rocket framework
|
||||
# see https://gitlab.com/sequoia-pgp/hagrid/issues/94
|
||||
if ($request_uri ~ "^/pks/lookup\?(.*search=)[^&<>]*\<(.+)\>(.*)") {
|
||||
set $left $1;
|
||||
set $middle $2;
|
||||
set $right $3;
|
||||
# index by fingerprint
|
||||
# gpg --search-keys <FINGEPRINT>
|
||||
location ~ "^/pks/internal/index/(?:0x)?([a-fA-F0-9]{40})$" {
|
||||
limit_req zone=search_fpr_keyid burst=30;
|
||||
limit_req_status 429;
|
||||
error_page 429 /errors-static/429-rate-limit-pks-index.htm;
|
||||
|
||||
set $args "";
|
||||
rewrite . /pks/lookup?$left$middle$right? break;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
# index by keyid
|
||||
# gpg --search-keys <KEYID>
|
||||
location ~ "^/pks/internal/index/(?:0x)?([a-fA-F0-9]{16})$" {
|
||||
limit_req zone=search_fpr_keyid burst=30;
|
||||
limit_req_status 429;
|
||||
error_page 429 /errors-static/429-rate-limit-pks-index.htm;
|
||||
|
||||
set $args "";
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
# index by email
|
||||
# gpg --search-keys <QUERY>
|
||||
location ~ ^/pks/internal/index/(.+(?:%40|@).+)$ {
|
||||
limit_req zone=search_email burst=50 nodelay;
|
||||
limit_req_status 429;
|
||||
error_page 429 /errors-static/429-rate-limit-pks-index.htm;
|
||||
|
||||
set $args "";
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
# forward to backend, which will like serve via x-accel-redirect
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
error_page 400 /errors-static/400-pks-invalid.htm;
|
||||
return 400;
|
||||
}
|
||||
|
||||
location /errors-static {
|
||||
internal;
|
||||
}
|
||||
|
||||
location /errors {
|
||||
internal;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_cache static_cache;
|
||||
}
|
||||
|
||||
location /search {
|
||||
limit_req zone=search_email burst=50 nodelay;
|
||||
error_page 429 /errors/429/rate-limit-web;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
@@ -112,10 +181,6 @@ location /verify {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
location /search {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
location /upload {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
@@ -130,6 +195,12 @@ location = / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
# cache "about" pages
|
||||
location = /atom.xml {
|
||||
proxy_cache static_cache;
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
# cache "about" pages
|
||||
location /about {
|
||||
proxy_cache static_cache;
|
||||
|
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "hagridctl"
|
||||
version = "0.1.0"
|
||||
authors = ["Vincent Breitmose <look@my.amazin.horse>"]
|
||||
authors = ["Vincent Breitmoser <look@my.amazin.horse>"]
|
||||
|
||||
[dependencies]
|
||||
hagrid-database = { path = "../database" }
|
||||
failure = "0.1.5"
|
||||
sequoia-openpgp = { version = "0.8", default-features = false }
|
||||
sequoia-openpgp = { version = "0.9", default-features = false }
|
||||
multipart = "0"
|
||||
log = "0"
|
||||
rand = "0.6"
|
||||
|
26
make-translated-templates
Executable file
26
make-translated-templates
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/zsh
|
||||
|
||||
for i in templates-untranslated/**/*.hbs; do
|
||||
local template=${${i#templates-untranslated/}}
|
||||
local prefix_file=${i:h}/template-prefix
|
||||
local suffix_file=${i:h}/template-suffix
|
||||
|
||||
echo -n "$template: "
|
||||
|
||||
echo -n "en "
|
||||
local dist_path=dist/templates/$template
|
||||
cat $prefix_file $i $suffix_file >! $dist_path
|
||||
|
||||
for translated in templates-translated/*/$template(N); do
|
||||
local locale=${${translated#templates-translated/}%%/*}
|
||||
local dist_path=dist/templates/localized/$locale/$template
|
||||
if [[ ! -d ${dist_path:h} ]]; then
|
||||
mkdir -p ${dist_path:h}
|
||||
fi
|
||||
echo -n "$locale "
|
||||
# echo "cat $prefix_file $translated $suffix_file >! $dist_path"
|
||||
cat $prefix_file $translated $suffix_file >! $dist_path
|
||||
done
|
||||
echo
|
||||
|
||||
done
|
@@ -1,5 +1,6 @@
|
||||
# allow 6 requests per min -> one each 10s on avg.
|
||||
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=6r/m;
|
||||
limit_req_zone $binary_remote_addr zone=search_email:10m rate=1r/m;
|
||||
limit_req_zone $binary_remote_addr zone=search_fpr_keyid:10m rate=5r/s;
|
||||
|
||||
proxy_cache_path /tmp/nginx_cache use_temp_path=off keys_zone=static_cache:10m;
|
||||
proxy_cache_valid 200 5m;
|
||||
|
461
po/hagrid/de.po
Normal file
461
po/hagrid/de.po
Normal file
@@ -0,0 +1,461 @@
|
||||
# Translators:
|
||||
# Vincent Breitmoser <look@my.amazin.horse>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
|
||||
"Last-Translator: Vincent Breitmoser <look@my.amazin.horse>, 2019\n"
|
||||
"Language-Team: German (https://www.transifex.com/otf/teams/102430/de/)\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr "Bestätige {userid} für deinen Schlüssel auf {domain}"
|
||||
|
||||
#: src/mail.rs:140
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr "Schlüssel-Verwaltung auf {domain}"
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr "Fehler"
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr "Die Operation hat einen internen Fehler versursacht :("
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr "<strong>Fehlermeldung:</strong> {{ internal_error }}"
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr "Die Anfrage verursachte einen Fehler:"
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr "Eintrag gefunden für <span class=\"email\">{{ query }}</span>:"
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid ""
|
||||
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
|
||||
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
|
||||
"our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
"<strong>Tip:</strong> Es ist bequemer, <span class=\"brand\">keys.openpgp."
|
||||
"org</span> aus OpenPGP-Software heraus zu verwenden. <br />\n"
|
||||
"Mehr dazu findest du in den <a href=\"/about/usage\">Nutzungshinweisen</a>."
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr "debug info"
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr "Suche nach Email-Adresse / Schlüssel-ID / Fingerprint"
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
msgstr ""
|
||||
"Du kannst deinen Schlüssel <a href=\"/upload\">hochladen</a> oder <a href=\"/"
|
||||
"manage\">verwalten</a>."
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr "Erfahre mehr <a href=\"/about\">über diesen Keyserver</a>."
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr "News:"
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (12.09.2019) (auf Englisch)"
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr "v{{ version }}, Revision"
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
"Hintergrund von <a href=\"https://www.toptal.com/designers/subtlepatterns/"
|
||||
"subtle-grey/\">Subtle Patterns</a> unter CC BY-SA 3.0"
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr "Wartungsarbeiten"
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr "Schlüssel verwalten"
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr "Email-Adresse des zu verwaltenden Schlüssels"
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr "Sende Link"
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
msgid ""
|
||||
"We will send you an email with a link you can use to remove any of your "
|
||||
"email addresses from search."
|
||||
msgstr ""
|
||||
"Du wirst eine Email mit einem Link erhalten, der es erlaubt, Adressen aus "
|
||||
"der Suche zu entfernen."
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
msgid ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Verwaltung des Schlüssels <span class=\"fingerprint\"><a href="
|
||||
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr "Dieser Schlüssel ist veröffentlicht mit diesen Identitäten:"
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr "Entfernen"
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Veröffentlichte Adressen können hier aus dem Schlüssel und der Suche "
|
||||
"entfernt werden. <br /> Um eine Adresse hinzuzufügen, muss der Schlüssel <a "
|
||||
"href=\"/upload\">erneut hochgeladen</a> werden."
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
msgid ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel ist jetzt ohne Identitäts-Informationen veröffentlicht. (<a "
|
||||
"href=\"/about\" target=\"_blank\">Was heisst das?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Um eine Identität hinzuzufügen, lade den Schlüssel <a href=\"/upload"
|
||||
"\">erneut hoch</a>."
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
msgid ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
"Eine Email mit den nächsten Schritten wurde an <span class=\"email"
|
||||
"\">{{ address }}</span> gesendet."
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
msgid "This address has already been verified."
|
||||
msgstr "Diese Adresse war bereits bestätigt."
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
msgid ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
"Dein Schlüssel <span class=\"fingerprint\">{{key_fpr}}</span> ist jetzt "
|
||||
"veröffentlicht mit der Identität <a href=\"{{userid_link}}\" target=\"_blank"
|
||||
"\"><span class=\"email\">{{ userid }}</span></a>."
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
msgid "Upload your key"
|
||||
msgstr "Schlüssel hochladen"
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr "Upload"
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
msgid ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
"Mehr Info? Wirf einen Blick auf unsere <a target=\"_blank\" href=\"/about"
|
||||
"\">Übersicht</a> und <a target=\"_blank\" href=\"/about/usage"
|
||||
"\">Nutzungshinweise</a>."
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
msgid ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Schlüssel <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
|
||||
"\"_blank\">{{key_fpr}}</a></span> erfolgreich hochgeladen."
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr "Dieser Schlüssel ist widerrufen."
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
msgid ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
|
||||
"mean?</a>)."
|
||||
msgstr ""
|
||||
"Er wird veröffentlicht ohne Identitäten, ist aber nicht für eine Suche nach "
|
||||
"Email-Adresse verfügbar. (<a href=\"/about\" target=\"_blank\">Was heisst "
|
||||
"das?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel ist mit folgenden Identitäten veröffentlicht (<a href=\"/"
|
||||
"about\" target=\"_blank\">was heisst das?</a>):"
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr "Veröffentlicht"
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
msgid ""
|
||||
"This key is now published with only non-identity information. (<a href=\"/"
|
||||
"about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel ist jetzt ohne Identitäts-Informationen veröffentlicht. (<a "
|
||||
"href=\"/about\" target=\"_blank\">Was heisst das?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
msgid ""
|
||||
"To make the key available for search by email address, you can verify it "
|
||||
"belongs to you:"
|
||||
msgstr ""
|
||||
"Um den Schlüssel für eine Suche nach Email-Adresse verfügbar zu machen, muss "
|
||||
"die entsprechende Adresse erst verifiziert werden:"
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr "Bestätigung wird erwartet"
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid ""
|
||||
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
|
||||
"prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
"<strong>Hinweis:</strong> Manche Provider verzögern den Empfang von Emails "
|
||||
"um bis zu 15 Minuten, um Spam zu verhindern. Bitte einen Moment Geduld."
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
msgid "Send Verification Email"
|
||||
msgstr "Bestätigungs-Email senden"
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
msgid ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel enthält eine Identität welche nicht als Email Adresse "
|
||||
"interpretiert werden konnte.<br /> Diese Identität kann nicht auf <span "
|
||||
"class=\"brand\">keys.openpgp.org</span> veröffentlich werden. (<a href=\"/"
|
||||
"about/faq#non-email-uids\" target=\"_blank\">Warum?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
msgid ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel enthält {{ count_unparsed }} Identitäten, die nicht als "
|
||||
"Email-Adressen erkannt werden konnten.<br /> Diese Identitäten können nicht "
|
||||
"auf <span class=\"brand\">keys.openpgp.org</span> veröffentlicht werden. (<a "
|
||||
"href=\"/about/faq#non-email-uids\" target=\"_blank\">Warum?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
msgid ""
|
||||
"This key contains one revoked identity, which is not published. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel enthält eine widerrufene Identität. Diese wird nicht "
|
||||
"veröffentlicht. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Warum?"
|
||||
"</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
msgid ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Dieser Schlüssel enthält {{ count_revoked }} widerrufene Identitäten. Diese "
|
||||
"werden nicht veröffentlicht. (<a href=\"/about/faq#revoked-uids\" target="
|
||||
"\"_blank\">Warum?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr "Schlüssel erfolgreich hochgeladen:"
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
msgid ""
|
||||
"<strong>Note:</strong> To make keys searchable by email address, you must "
|
||||
"upload them individually."
|
||||
msgstr ""
|
||||
"<strong>Hinweis:</strong> Um Schlüssel für die Email-Adresssuche zu "
|
||||
"bestätigen, müssen sie jeweils einzeln hochgeladen werden."
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr "Email-Adresse wird bestätigt..."
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
msgid ""
|
||||
"If the process doesn't complete after a few seconds, please <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
"Wenn der Vorgang nicht in einigen Sekunden erfolgreich ist, bitte<input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"hier klicken\"/>."
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr "Schlüssel-Verwaltung auf {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr "Hi,"
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
msgid ""
|
||||
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
"Dies ist eine automatisierte Nachricht von <a href=\"{{base_uri}}\" style="
|
||||
"\"text-decoration:none; color: #333\">{{ domain }}</a>."
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr "Falls dies unerwartet ist, bitte die Nachricht ignorieren."
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr "OpenPGP Schlüssel: <tt>{{primary_fp}}</tt>"
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
msgstr ""
|
||||
"Du kannst die Identitäten dieses Schlüssels unter folgendem Link verwalten:"
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
"Weiter Informationen findest du unter <a href=\"{{base_uri}}/about"
|
||||
"\">{{domain}}/about</a>."
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr "Verzeichnis für OpenPGP-Schlüssel seit 2019"
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr "Dies ist eine automatische Nachricht von {{domain}}."
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr "OpenPGP Schlüssel: {{primary_fp}}"
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr "Weiter Informationen findest du unter {{base_uri}}/about"
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr "Bestätige {{userid}} für deinen Schlüssel auf {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
msgstr ""
|
||||
"Damit der Schlüssel über die Email-Adresse \"<a rel=\"nofollow\" href=\"#\" "
|
||||
"style=\"text-decoration:none; color: #333\">{{userid}}</a>\" gefunden werden "
|
||||
"kann, klicke den folgenden Link:"
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"{{userid}}\",\n"
|
||||
"please follow the link below:"
|
||||
msgstr ""
|
||||
"Damit der Schlüssel über die Email-Adresse \"{{userid}}\" gefunden werden "
|
||||
"kann,\n"
|
||||
"klicke den folgenden Link:"
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr "Dieser Link ist ungültig, oder bereits abgelaufen."
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr "Ungültiges Adress-Format: {address}"
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr "Kein Schlüssel gefunden für {address}"
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr "Eine E-Mail für diesen Schlüssel wurde erst kürzlich versandt."
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr "Fehler bei Verarbeitung des Schlüssel-Materials."
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr "Ups, bitte keine geheimen Schlüssel hochladen!"
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
msgid "No key uploaded."
|
||||
msgstr "Es wurde kein Schlüssel hochgeladen."
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr "Fehler bei Verarbeitung des hochgeladenen Schlüssels."
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr "Zeitlimit beim Hochladen abgelaufen. Bitte versuch es erneut."
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
msgid "Invalid verification link."
|
||||
msgstr "Ungültiger Bestätigungs-Link."
|
640
po/hagrid/en.po
Normal file
640
po/hagrid/en.po
Normal file
@@ -0,0 +1,640 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2018-06-15 16:33-0700\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
#, fuzzy
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr "Verify {{userid}} for your key on {{domain}}"
|
||||
|
||||
#: src/mail.rs:140
|
||||
#, fuzzy
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr "Manage your key on {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr "Looks like something went wrong :("
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
#, fuzzy
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr "<strong>Error:</strong> {{ internal_error }}"
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
#, fuzzy
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid ""
|
||||
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
|
||||
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
|
||||
"our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr "debug info"
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr "Search by Email Address / Key ID / Fingerprint"
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr "Search"
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
msgstr ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr "Find out more <a href=\"/about\">about this service</a>."
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr "News:"
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
#, fuzzy
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr "v{{ version }} built from"
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr "Maintenance Mode"
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr "Manage your key"
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
#, fuzzy
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr "Enter any verified e-mail address of your key"
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr "Send link"
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"We will send you an email with a link you can use to remove any of your "
|
||||
"email addresses from search."
|
||||
msgstr ""
|
||||
"We will send you an e-mail with a link you can use to remove any of your e-"
|
||||
"mail addresses from search."
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr "Your key is published with the following identity information:"
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">what does this mean?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>"
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
#, fuzzy
|
||||
msgid "This address has already been verified."
|
||||
msgstr "This address was already verified."
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
#, fuzzy
|
||||
msgid "Upload your key"
|
||||
msgstr "Manage your key"
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr "Upload"
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>!"
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr "This key is revoked."
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
|
||||
"mean?</a>)."
|
||||
msgstr ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by e-mail address"
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr "Published"
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"This key is now published with only non-identity information. (<a href=\"/"
|
||||
"about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"This key is now published with only non-identity information (<a href=\"/"
|
||||
"about\" target=\"_blank\">what does this mean?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"To make the key available for search by email address, you can verify it "
|
||||
"belongs to you:"
|
||||
msgstr ""
|
||||
"To make the key available for search by e-mail address, you can verify it "
|
||||
"belongs to you:"
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr "Verification Pending"
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid ""
|
||||
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
|
||||
"prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
#, fuzzy
|
||||
msgid "Send Verification Email"
|
||||
msgstr "Send Verification Mail"
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">Why?</a>)"
|
||||
msgstr ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">why?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">why?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"This key contains one revoked identity, which is not published. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr "This key contains one revoked identity, which is not published."
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published."
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr "Your keys have been successfully uploaded:"
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"<strong>Note:</strong> To make keys searchable by email address, you must "
|
||||
"upload them individually."
|
||||
msgstr ""
|
||||
"<strong>Note:</strong> To make keys searchable by address, you must upload "
|
||||
"them individually."
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr "Verifying your email address…"
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"If the process doesn't complete after a few seconds, please <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
"If the process doesn't complete after a few seconds, <input type=\"submit\" "
|
||||
"class=\"textbutton\" value=\"click here\" />."
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr "Manage your key on {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr "Hi,"
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
"this is an automated message from <a rel=\"nofollow\" href="
|
||||
"\"{{ base_uri }}\" style=\"text-decoration:none; color: "
|
||||
"#333\"><tt>{{ domain }}</tt></a>."
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr "If you didn't request this message, please ignore it."
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
msgstr ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
#, fuzzy
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr "this is an automated message from {{domain}}. If you didn't"
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr "OpenPGP key: {{primary_fp}}"
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr "You can find more info at {{base_uri}}/about"
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr "Verify {{userid}} for your key on {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
msgstr ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"{{userid}}\",\n"
|
||||
"please follow the link below:"
|
||||
msgstr "To let others find this key from your email address \"{{userid}}\","
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
#, fuzzy
|
||||
msgid "No key uploaded."
|
||||
msgstr "Your key upload on {}"
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
#, fuzzy
|
||||
msgid "Invalid verification link."
|
||||
msgstr "Send Verification Mail"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgctxt "Subject for welcome email"
|
||||
#~ msgid "Your key upload on {domain}"
|
||||
#~ msgstr "Your key upload on {{domain}}"
|
||||
|
||||
#~ msgid "Your key upload on {{domain}}"
|
||||
#~ msgstr "Your key upload on {{domain}}"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "If you didn't upload your key there, please ignore this message."
|
||||
#~ msgstr ""
|
||||
#~ "this is an automated message from {{domain}}. If you didn't upload your "
|
||||
#~ "key"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "This key was just uploaded for the first time, and is now published "
|
||||
#~ "without identity information. If you want to allow others to find this "
|
||||
#~ "key by e-mail address, please follow this link:"
|
||||
#~ msgstr ""
|
||||
#~ "This key was just uploaded for the first time, and is now published "
|
||||
#~ "without identity information. If you want to allow others to find this "
|
||||
#~ "key by e-mail address, please follow this link:"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "This key was just uploaded for the first time, and is now published "
|
||||
#~ "without\n"
|
||||
#~ "identity information. If you want to allow others to find this key by e-"
|
||||
#~ "mail\n"
|
||||
#~ "address, please follow this link:"
|
||||
#~ msgstr ""
|
||||
#~ "This key was just uploaded for the first time, and is now published "
|
||||
#~ "without identity information. If you want to allow others to find this "
|
||||
#~ "key by e-mail address, please follow this link:"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "To manage and delete listed addresses on this key, please follow\n"
|
||||
#~ "the link below:"
|
||||
#~ msgstr ""
|
||||
#~ "To manage and delete listed addresses on this key, please follow the link "
|
||||
#~ "below:"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "this is an automated message from {{domain}}. If you didn't upload your "
|
||||
#~ "key\n"
|
||||
#~ "there, please ignore this message."
|
||||
#~ msgstr ""
|
||||
#~ "this is an automated message from {{domain}}. If you didn't upload your "
|
||||
#~ "key"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid ""
|
||||
#~ "this is an automated message from {{domain}}. If you didn't\n"
|
||||
#~ "request this message, please ignore it."
|
||||
#~ msgstr ""
|
||||
#~ "this is an automated message from {{domain}}. If you didn't upload your "
|
||||
#~ "key"
|
||||
|
||||
#~ msgid "OpenPGP key: <tt>{{ primary_fp }}</tt>"
|
||||
#~ msgstr "OpenPGP key: <tt>{{ primary_fp }}</tt>"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "You can find more info at <a href=\"{{ base_uri }}/about\">{{ domain }}/"
|
||||
#~ "about</a>."
|
||||
#~ msgstr ""
|
||||
#~ "You can find more info at <a href=\"{{ base_uri }}/about\">{{ domain }}/"
|
||||
#~ "about</a>."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "this is an automated message from <a rel=\"nofollow\" href="
|
||||
#~ "\"{{base_uri}}\" style=\"text-decoration:none; color: "
|
||||
#~ "#333\"><tt>{{domain}}</tt></a>. If you didn't request this message, "
|
||||
#~ "please ignore it."
|
||||
#~ msgstr ""
|
||||
#~ "this is an automated message from <a rel=\"nofollow\" href="
|
||||
#~ "\"{{base_uri}}\" style=\"text-decoration:none; color: "
|
||||
#~ "#333\"><tt>{{domain}}</tt></a>. If you didn't request this message, "
|
||||
#~ "please ignore it."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "this is an automated message from <a rel=\"nofollow\" href="
|
||||
#~ "\"{{base_uri}}\" style=\"text-decoration:none; color: "
|
||||
#~ "#333\"><tt>{{domain}}</tt></a>. If you didn't upload your key there, "
|
||||
#~ "please ignore this message."
|
||||
#~ msgstr ""
|
||||
#~ "this is an automated message from <a rel=\"nofollow\" href="
|
||||
#~ "\"{{base_uri}}\" style=\"text-decoration:none; color: "
|
||||
#~ "#333\"><tt>{{domain}}</tt></a>. If you didn't upload your key there, "
|
||||
#~ "please ignore this message."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Greetings from the <a rel=\"nofollow\" href=\"{{ base_uri }}\" style="
|
||||
#~ "\"text-decoration:none; color: #333\"><tt>keys.openpgp.org</tt></a> team"
|
||||
#~ msgstr ""
|
||||
#~ "Greetings from the <a rel=\"nofollow\" href=\"{{ base_uri }}\" style="
|
||||
#~ "\"text-decoration:none; color: #333\"><tt>keys.openpgp.org</tt></a> team"
|
||||
|
||||
#~ msgid "Greetings from the keys.openpgp.org team"
|
||||
#~ msgstr "Greetings from the keys.openpgp.org team"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Greetings from the <a rel=\"nofollow\" href=\"{{base_uri}}\" style=\"text-"
|
||||
#~ "decoration:none; color: #333\"><tt>keys.openpgp.org</tt></a> team"
|
||||
#~ msgstr ""
|
||||
#~ "Greetings from the <a rel=\"nofollow\" href=\"{{base_uri}}\" style=\"text-"
|
||||
#~ "decoration:none; color: #333\"><tt>keys.openpgp.org</tt></a> team"
|
||||
|
||||
#~ msgid "Verification failed! Perhaps the link you used was expired?"
|
||||
#~ msgstr "Verification failed! Perhaps the link you used was expired?"
|
||||
|
||||
#~ msgid "Your public key"
|
||||
#~ msgstr "Your public key"
|
||||
|
||||
#~ msgid "You can <a href=\"/upload\">try uploading again</a>."
|
||||
#~ msgstr "You can <a href=\"/upload\">try uploading again</a>."
|
||||
|
||||
#, fuzzy
|
||||
#~ msgctxt "Subject for verification email"
|
||||
#~ msgid "Verify {email} for your key on {domain}"
|
||||
#~ msgstr "Verify {{userid}} for your key on {{domain}}"
|
||||
|
||||
#~ msgid "what does this mean?"
|
||||
#~ msgstr "what does this mean?"
|
||||
|
||||
#~ msgid "Verify {} for your key on {}"
|
||||
#~ msgstr "Verify {} for your key on {}"
|
||||
|
||||
#~ msgid "Manage your key on {}"
|
||||
#~ msgstr "Manage your key on {}"
|
||||
|
||||
#~ msgid "(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
#~ msgstr "(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
|
||||
#~ msgid "request this message, please ignore it."
|
||||
#~ msgstr "request this message, please ignore it."
|
||||
|
||||
#~ msgid "please follow the link below:"
|
||||
#~ msgstr "please follow the link below:"
|
||||
|
||||
#~ msgid "there, please ignore this message."
|
||||
#~ msgstr "there, please ignore this message."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "This key was just uploaded for the first time, and is now published "
|
||||
#~ "without"
|
||||
#~ msgstr ""
|
||||
#~ "This key was just uploaded for the first time, and is now published "
|
||||
#~ "without"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "identity information. If you want to allow others to find this key by e-"
|
||||
#~ "mail"
|
||||
#~ msgstr ""
|
||||
#~ "identity information. If you want to allow others to find this key by e-"
|
||||
#~ "mail"
|
||||
|
||||
#~ msgid "address, please follow this link:"
|
||||
#~ msgstr "address, please follow this link:"
|
||||
|
||||
#~ msgid "To manage and delete listed addresses on this key, please follow"
|
||||
#~ msgstr "To manage and delete listed addresses on this key, please follow"
|
||||
|
||||
#~ msgid "the link below:"
|
||||
#~ msgstr "the link below:"
|
468
po/hagrid/fr.po
Normal file
468
po/hagrid/fr.po
Normal file
@@ -0,0 +1,468 @@
|
||||
# Translators:
|
||||
# AO <ao@localizationlab.org>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
|
||||
"Last-Translator: AO <ao@localizationlab.org>, 2019\n"
|
||||
"Language-Team: French (https://www.transifex.com/otf/teams/102430/fr/)\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr "Confirmer {userid} pour votre clé sur {domain}"
|
||||
|
||||
#: src/mail.rs:140
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr "Gérer votre clé sur {domain}"
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr "Il semble qu’un problème est survenu :("
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr "Message d’erreur : {{ internal_error }}"
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr "Votre demande a généré une erreur :"
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr ""
|
||||
"Nous avons trouvé une entrée pour <span class=\"email\">{{ query }}</span> :"
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid ""
|
||||
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
|
||||
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
|
||||
"our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
"<strong>Conseil :</strong> Il est plus pratique d’utiliser <span class="
|
||||
"\"brand\">keys.openpgp.org</span> à partir de votre logiciel OpenPGP.<br /> "
|
||||
"Vous trouverez plus de précisions dans notre <a href=\"/about/usage\">guide "
|
||||
"d’utilisation</a>."
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr "renseignements de débogage"
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr "Cherchez par adresse courriel / ID de clé / empreinte"
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr "Chercher"
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
msgstr ""
|
||||
"Vous pouvez aussi <a href=\"/upload\">téléverser</a> ou <a href=\"/manage"
|
||||
"\">gérer</a> votre clé."
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr "En apprendre davantage <a href=\"/about\">sur ce service</a>."
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr "Nouvelles :"
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Trois mois après le "
|
||||
"lancement ✨</a> (12-09-2019) (page en anglais)"
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr "v{{ version }} compilée à partir de"
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr ""
|
||||
"Propulsé par <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a> (site en "
|
||||
"anglais)"
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
"L’image d’arrière-plan a été obtenue de <a href=\"https://www.toptal.com/"
|
||||
"designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> sous licence CC "
|
||||
"BY-SA 3.0"
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr "Mode de maintenance"
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr "Gérer votre clé"
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr "Saisissez une adresse courriel confirmée pour votre clé"
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr "Envoyer le lien"
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
msgid ""
|
||||
"We will send you an email with a link you can use to remove any of your "
|
||||
"email addresses from search."
|
||||
msgstr ""
|
||||
"Nous vous enverrons un courriel avec un lien que vous pourrez utiliser pour "
|
||||
"supprimer de la recherche l’une de vos adresses courriel."
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
msgid ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Gestion de la clé <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr "Votre clé est publiée avec les renseignements d’identité suivants :"
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Cliquer sur « Supprimer » pour l’une des adresses l’enlèvera de cette clé. "
|
||||
"Elle n’apparaîtra plus lors d’une recherche.<br /> Pour ajouter une autre "
|
||||
"adresse, <a href=\"/upload\">téléversez</a> la clé de nouveau."
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
msgid ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Votre clé n’est publiée qu’en tant que renseignement qui ne permet pas de "
|
||||
"vous identifier. (<a href=\"/about\" target=\"_blank\">Qu’est-ce que cela "
|
||||
"signifie ?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Pour ajouter une adresse, <a href=\"/upload\">téléversez</a> la clé de "
|
||||
"nouveau."
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
msgid ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
"Un courriel avec de plus amples instructions a été envoyé à <span class="
|
||||
"\"email\">{{ address }}</span>."
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
msgid "This address has already been verified."
|
||||
msgstr "Cette adresse a déjà été confirmée."
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
msgid ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
"Votre clé <span class=\"fingerprint\">{{ key_fpr }}</span> est maintenant "
|
||||
"publiée pour l’identité <a href=\"{{userid_link}}\" target=\"_blank\"><span "
|
||||
"class=\"email\">{{ userid }}</span></a>. "
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
msgid "Upload your key"
|
||||
msgstr "Téléverser votre clé"
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr "Téléverser"
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
msgid ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
"Besoin de plus de précisions ? Consultez notre <a target=\"_blank\" href=\"/"
|
||||
"about\">présentation</a> et notre <a target=\"_blank\" href=\"/about/usage"
|
||||
"\">guide d’utilisation</a>."
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
msgid ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Vous avez téléversé la clé <span class=\"fingerprint\"><a href="
|
||||
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr "Cette clé est révoquée."
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
msgid ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
|
||||
"mean?</a>)."
|
||||
msgstr ""
|
||||
"Elle est publiée sans renseignements d’identité et une recherche par adresse "
|
||||
"courriel ne la trouvera pas (<a href=\"/about\" target=\"_blank\">qu’est-ce "
|
||||
"que cela signifie ?</a>)."
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
"Cette clé est maintenant publiée avec les renseignements d’identité suivants "
|
||||
"(<a href=\"/about\" target=\"_blank\">qu’est-ce que cela signifie ?</a>) :"
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr "Publiée"
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
msgid ""
|
||||
"This key is now published with only non-identity information. (<a href=\"/"
|
||||
"about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Cette clé est maintenant publiée avec seulement des renseignements qui ne "
|
||||
"permettent pas de vous identifier. (<a href=\"/about\" target=\"_blank"
|
||||
"\">Qu’est-ce que cela signifie ?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
msgid ""
|
||||
"To make the key available for search by email address, you can verify it "
|
||||
"belongs to you:"
|
||||
msgstr ""
|
||||
"Afin qu’une recherche par adresse courriel trouve cette clé, vous pouvez "
|
||||
"confirmer qu’elle vous appartient :"
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr "La confirmation est en attente"
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid ""
|
||||
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
|
||||
"prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
"<strong>Note :</strong> Certains fournisseurs retardent les courriels "
|
||||
"jusqu’à 15 minutes afin de prévenir les courriels indésirables (pourriels). "
|
||||
"Veuillez faire preuve de patience."
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
msgid "Send Verification Email"
|
||||
msgstr "Envoyer un courriel de confirmation"
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
msgid ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Cette clé comprend une identité qui n’a pas pu être analysée en tant "
|
||||
"qu’adresse courriel.<br /> Cette identité ne peut pas être publiée sur <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids"
|
||||
"\" target=\"_blank\">Pourquoi ?</a>) "
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
msgid ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Cette clé comprend {{ count_unparsed }} identités qui n’ont pas pu être "
|
||||
"analysées en tant qu’adresse courriel.<br /> Ces identités ne peuvent pas "
|
||||
"être publiées sur <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/"
|
||||
"about/faq#non-email-uids\" target=\"_blank\">Pourquoi ?</a>) "
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
msgid ""
|
||||
"This key contains one revoked identity, which is not published. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Cette clé comprend une identité révoquée qui n’est pas publiée. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Pourquoi ?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
msgid ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Cette clé comprend {{ count_revoked }} identités révoquées qui ne sont pas "
|
||||
"publiées. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Pourquoi ?</"
|
||||
"a>)"
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr "Vos clés ont été téléversées avec succès :"
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
msgid ""
|
||||
"<strong>Note:</strong> To make keys searchable by email address, you must "
|
||||
"upload them individually."
|
||||
msgstr ""
|
||||
"<strong>Note :</strong> Afin qu’une recherche par adresse courriel trouve "
|
||||
"des clés, vous devez les téléverser individuellement."
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr "Confirmation de votre adresse courriel…"
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
msgid ""
|
||||
"If the process doesn't complete after a few seconds, please <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
"Si le processus n’est pas terminé après quelques secondes, veuillez <input "
|
||||
"type=\"submit\" class=\"textbutton\" value=\"cliquer ici\"/>."
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr "Gérer votre clé sur {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr "Bonjour,"
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
msgid ""
|
||||
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
"Ceci est un courriel automatisé de <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr "Si vous n’avez pas demandé ce courriel, veuillez l’ignorer."
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr "Clé OpenPGP : <tt>{{primary_fp}}</tt>"
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
msgstr ""
|
||||
"Pour gérer et supprimer les adresses répertoriées de cette clé, veuillez "
|
||||
"suivre le lien ci-dessous :"
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
"Pour de plus amples renseignements, consultez <a href=\"{{base_uri}}/about"
|
||||
"\">{{domain}}/about</a>."
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr "distribue des clés OpenPGP depuis 2019"
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr "Ceci est un courriel automatisé de {{domain}}."
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr "Clé OpenPGP : {{primary_fp}}"
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr "Pour de plus amples renseignements, consultez {{base_uri}}/about"
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr "Confirmer {{userid}} pour votre clé sur {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
msgstr ""
|
||||
"Afin de permettre à d’autres de trouver cette clé à partir de votre adresse "
|
||||
"courriel « <a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; "
|
||||
"color : #333\">{{userid}}</a> », veuillez cliquer sur le lien ci-dessous :"
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"{{userid}}\",\n"
|
||||
"please follow the link below:"
|
||||
msgstr ""
|
||||
"Afin de permettre à d’autres de trouver cette clé à partir de votre\n"
|
||||
"adresse courriel « {{userid}} », veuillez suivre le lien ci-dessous :"
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr "Ce lien est invalide ou expiré"
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr "Cette adresse est malformée :"
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr "Il n’y a pas de clé pour cette adresse : {address}"
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr "Une demande a déjà été envoyée récemment pour cette adresse."
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr "Échec d’analyse des données de la clé."
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr "Attention : Veuillez ne pas téléverser de clés secrètes !"
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
msgid "No key uploaded."
|
||||
msgstr "Aucune clé n’a été téléversée."
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr "Erreur de traitement de la clé téléversée."
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr "La session de téléversement est expirée. Veuillez ressayer."
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
msgid "Invalid verification link."
|
||||
msgstr "Le lien de confirmation est invalide."
|
323
po/hagrid/hagrid.pot
Normal file
323
po/hagrid/hagrid.pot
Normal file
@@ -0,0 +1,323 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr ""
|
||||
|
||||
#: src/mail.rs:140
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid "<strong>Hint:</strong> It's more convenient to use <span class=\"brand\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid "You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</a> your key."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid "<a href=\"/about/news#2019-09-12-three-months-later\">Three months after launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid "Background image retrieved from <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
msgid "We will send you an email with a link you can use to remove any of your email addresses from search."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
msgid "Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid "Clicking \"delete\" on any address will remove it from this key. It will no longer appear in a search.<br /> To add another address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
msgid "Your key is published as only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
msgid "We have sent an email with further instructions to <span class=\"email\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
msgid "This address has already been verified."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
msgid "Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
msgid "Upload your key"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
msgid "Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and <a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
msgid "You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
msgid "It is published without identity information and can't be made available for search by email address (<a href=\"/about\" target=\"_blank\">what does this mean?</a>)."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid "This key is now published with the following identity information (<a href=\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
msgid "This key is now published with only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
msgid "To make the key available for search by email address, you can verify it belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid "<strong>Note:</strong> Some providers delay emails for up to 15 minutes to prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
msgid "Send Verification Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
msgid "This key contains one identity that could not be parsed as an email address.<br /> This identity can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
msgid "This key contains {{ count_unparsed }} identities that could not be parsed as an email address.<br /> These identities can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
msgid "This key contains one revoked identity, which is not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
msgid "This key contains {{ count_revoked }} revoked identities, which are not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
msgid "<strong>Note:</strong> To make keys searchable by email address, you must upload them individually."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
msgid "If the process doesn't complete after a few seconds, please <input type=\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
msgid "This is an automated message from <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid "To manage and delete listed addresses on this key, please follow the link below:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid "You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</a>."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid "To let others find this key from your email address \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", please click the link below:"
|
||||
msgstr ""
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
msgid "To let others find this key from your email address \"{{userid}}\",\nplease follow the link below:"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
msgid "No key uploaded."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
msgid "Invalid verification link."
|
||||
msgstr ""
|
460
po/hagrid/pl.po
Normal file
460
po/hagrid/pl.po
Normal file
@@ -0,0 +1,460 @@
|
||||
# Translators:
|
||||
# Wiktor Kwapisiewicz, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
|
||||
"Last-Translator: Wiktor Kwapisiewicz, 2019\n"
|
||||
"Language-Team: Polish (https://www.transifex.com/otf/teams/102430/pl/)\n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
|
||||
"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
|
||||
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr "Zweryfikuj {userid} dla twojego klucza na {domain}"
|
||||
|
||||
#: src/mail.rs:140
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr "Zarządzaj swoim kluczem na {domain}"
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr "Błąd"
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr "Wygląda na to, że coś poszło nie tak :("
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr "Komunikat błędu: {{ internal_error }}"
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr "Podczas przetwarzania twojego zapytania wystąpił błąd:"
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr "Znaleźliśmy wpis dla <span class=\"email\">{{ query }}</span>:"
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid ""
|
||||
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
|
||||
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
|
||||
"our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
"<strong>Podpowiedź:</strong> Wygodniej używać <span class=\"brand\">keys."
|
||||
"openpgp.org</span> z twojego oprogramowania OpenPGP.<br /> Sprawdź nasz <a "
|
||||
"href=\"/about/usage\">poradnik</a> aby dowiedzieć się więcej."
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr "informacje diagnostyczne"
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr "Wyszukaj po adresie e-mail / ID klucza / odcisku"
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr "Wyszukaj"
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
msgstr ""
|
||||
"Możesz także <a href=\"/upload\">wgrać</a> lub <a href=\"/manage"
|
||||
"\">zarządzać</a> swoim kluczem."
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr "Dowiedz się więcej <a href=\"/about\">o tej usłudze</a>."
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr "Nowości:"
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Trzy miesiące od "
|
||||
"startu ✨</a> (2019-09-12)"
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr "v{{ version }} zbudowana z "
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr "Używa <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
"Tło pobrane z <a href=\"https://www.toptal.com/designers/subtlepatterns/"
|
||||
"subtle-grey/\">Subtle Patterns</a> na licencji CC BY-SA 3.0"
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr "Tryb Serwisowania"
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr "Zarządzaj kluczem"
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr "Wpisz dowolny zweryfikowany adres swojego klucza"
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr "Wyślij łącze"
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
msgid ""
|
||||
"We will send you an email with a link you can use to remove any of your "
|
||||
"email addresses from search."
|
||||
msgstr ""
|
||||
"Wyślemy ci e-mail z łączem którego możesz użyć do usunięcia dowolnego adresu "
|
||||
"z wyszukiwania."
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
msgid ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Zarządzanie kluczem <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>"
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr ""
|
||||
"Twój klucz został opublikowany z następującymi informacjami tożsamości:"
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr "Usuń"
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Klikając \"usuń\" na dowolnym adresie usunie ten adres z klucza. Nie będzie "
|
||||
"on wyświetlany podczas wyszukiwania.<br /> Aby dodać inny adres <a href=\"/"
|
||||
"upload\">wyślij</a> klucz ponownie."
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
msgid ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Twój klucz jest opublikowany bez informacji o tożsamości. (<a href=\"/about"
|
||||
"\" target=\"_blank\">Co to oznacza?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr "Aby dodać adres, <a href=\"/upload\">wyślij</a> klucz ponownie."
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
msgid ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
"Wysłaliśmy e-mail z dalszymi instrukcjami pod <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
msgid "This address has already been verified."
|
||||
msgstr "Adres został już zweryfikowany."
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
msgid ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
"Twój klucz <span class=\"fingerprint\">{{ key_fpr }}</span> jest "
|
||||
"opublikowany dla tożsamości <a href=\"{{userid_link}}\" target=\"_blank"
|
||||
"\"><span class=\"email\">{{ userid }}</span></a>."
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
msgid "Upload your key"
|
||||
msgstr "Wyślij swój klucz"
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr "Wyślij"
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
msgid ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
"Potrzebujesz więcej informacji? Sprawdź nasze <a target=\"_blank\" href=\"/"
|
||||
"about\">wprowadzenie</a> oraz <a target=\"_blank\" href=\"/about/usage"
|
||||
"\">instrukcję użytkowania</a>."
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
msgid ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"Wysłałeś klucz <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
|
||||
"\"_blank\">{{ key_fpr }}</a></span>."
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr "Ten klucz jest unieważniony."
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
msgid ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
|
||||
"mean?</a>)."
|
||||
msgstr ""
|
||||
"Jest opublikowany bez informacji o tożsamości i nie można go znaleźć za "
|
||||
"pomocą adresu e-mail (<a href=\"/about\" target=\"_blank\">co to znaczy?</"
|
||||
"a>)."
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
"Ten klucz jest teraz opublikowany z następującymi informacjami tożsamości "
|
||||
"(<a href=\"/about\" target=\"_blank\">co to znaczy?</a>);"
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr "Opublikowany"
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
msgid ""
|
||||
"This key is now published with only non-identity information. (<a href=\"/"
|
||||
"about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Ten klucz jest opublikowany bez informacji o tożsamości (<a href=\"/about\" "
|
||||
"target=\"_blank\">Co to oznacza?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
msgid ""
|
||||
"To make the key available for search by email address, you can verify it "
|
||||
"belongs to you:"
|
||||
msgstr ""
|
||||
"Aby można było wyszukać ten klucz używając adresu e-mail, zweryfikuj, że "
|
||||
"należy do ciebie:"
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr "Czekam na weryfikację"
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid ""
|
||||
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
|
||||
"prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
"<strong>Informacja:</strong> Niektórzy dostawcy poczty opóźniają e-maile do "
|
||||
"15 minut aby zapobiegać spamowi. Proszę być cierpliwym."
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
msgid "Send Verification Email"
|
||||
msgstr "Wyślij E-mail Weryfikacyjny"
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
msgid ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Ten klucz posiada jedną tożsamość z której nie można wyłuskać adresu e-mail."
|
||||
"<br /> Ta tożsamość nie może zostać opublikowana na <span class=\"brand"
|
||||
"\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target="
|
||||
"\"_blank\">Dlaczego?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
msgid ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Ten klucz zawiera {{ count_unparsed }} tożsamości z których nie da się "
|
||||
"wyłuskać adresów e-mail.<br /> Te tożsamości nie mogą zostać opublikowane na "
|
||||
"<span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-"
|
||||
"email-uids\" target=\"_blank\">Dlaczego?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
msgid ""
|
||||
"This key contains one revoked identity, which is not published. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Ten klucz zwiera jedną unieważnioną tożsamość które nie została "
|
||||
"opublikowana. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
|
||||
"\">Dlaczego?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
msgid ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Ten klucz zwiera {{ count_revoked }} unieważnionych tożsamości które nie "
|
||||
"zostały opublikowane. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
|
||||
"\">Dlaczego?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr "Twoje klucze zostały pomyślnie wysłane:"
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
msgid ""
|
||||
"<strong>Note:</strong> To make keys searchable by email address, you must "
|
||||
"upload them individually."
|
||||
msgstr ""
|
||||
"<strong>Informacja:</strong> Aby można było wyszukać kluczy za pomocą adresu "
|
||||
"e-mail, wyślij je pojedynczo."
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr "Weryfikowanie twojego adresu e-mail..."
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
msgid ""
|
||||
"If the process doesn't complete after a few seconds, please <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
"Jeśli proces nie zakończy się po kilku sekundach proszę <input type=\"submit"
|
||||
"\" class=\"textbutton\" value=\"kliknij tutaj\"/>."
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr "Zarządzaj swoim kluczem na {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr "Cześć,"
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
msgid ""
|
||||
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
"To jest automatyczna wiadomość od <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr "Jeśli nie prosiłeś o tą wiadomość, zignorują ją."
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr "Klucz OpenPGP: <tt>{{primary_fp}}</tt>"
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
msgstr "Aby zarządzać adresami na kluczu lub je usunąć, użyj tego łącza:"
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
"Więcej informacji znajdziesz na <a href=\"{{base_uri}}/about\">{{domain}}/"
|
||||
"about</a>."
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr "dostarczanie kluczy OpenPGP od 2019"
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr "To automatyczna wiadomość z {{domain}}."
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr "Klucz OpenPGP: {{primary_fp}}"
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr "Znajdziesz więcej informacji na {{base_uri}}/about"
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr "Zweryfikuj {{userid}} dla klucza na {{domain}}"
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
msgstr ""
|
||||
"Aby inni mogli wyszukać twój klucz przez adres e-mail \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"proszę kliknąć w łącze poniżej:"
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"{{userid}}\",\n"
|
||||
"please follow the link below:"
|
||||
msgstr ""
|
||||
"Aby inni mogli wyszukać twój klucz przez adres e-mail \"{{userid}}\", proszę "
|
||||
"kliknąć w łącze poniżej:"
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr "Łącze jest nieważne lub wygasło"
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr "Nieprawidłowy adres: {address}"
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr "Brak klucza dla adresu: {address}"
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr "Zapytanie o ten adres zostało niedawno wysłane."
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr "Przetwarzanie danych klucza się nie powiodło."
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr "Ups, nie wysyłaj kluczy prywatnych!"
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
msgid "No key uploaded."
|
||||
msgstr "Klucz nie został wysłany."
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr "Błąd przetwarzania wysłanego klucza."
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr "Sesja wysyłania wygasła. Spróbuj ponownie."
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
msgid "Invalid verification link."
|
||||
msgstr "Nieprawidłowy link weryfikacyjny."
|
459
po/hagrid/tr.po
Normal file
459
po/hagrid/tr.po
Normal file
@@ -0,0 +1,459 @@
|
||||
# Translators:
|
||||
# T. E. Kalayci <tekrei@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
|
||||
"Last-Translator: T. E. Kalayci <tekrei@gmail.com>, 2019\n"
|
||||
"Language-Team: Turkish (https://www.transifex.com/otf/teams/102430/tr/)\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr "{domain} üzerindeki anahtarınız için {userid} doğrulaması"
|
||||
|
||||
#: src/mail.rs:140
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr "{domain} sunucusundaki anahtarınızı yönetin"
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr "Hata"
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr "Bir şeyler ters gitti gibi gözüküyor :("
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr "Hata mesajı: {{ internal_error }}"
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr "İsteğinizle ilgili bir hata oluştu:"
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr "<span class=\"email\">{{ query }}</span> için bir girdi bulundu:"
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid ""
|
||||
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
|
||||
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
|
||||
"our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
"<strong>İpucu:</strong> <span class=\"brand\">keys.openpgp.org</span> "
|
||||
"hizmetini OpenPGP yazılımından kullanmak daha elverişlidir.<br />Ayrıntılar "
|
||||
"için <a href=\"/about/usage\">kullanma kılavuzumuza</a> bakabilirsiniz."
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr "hata ayıklama bilgileri"
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr "E-posta adresi / Anahtar Kimliği / Parmak İzi ile arama"
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr "Ara"
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
msgstr ""
|
||||
"Ayrıca anahtarınızı <a href=\"/upload\">yükleyebilir</a> veya <a href=\"/"
|
||||
"manage\">yönetebilirsiniz</a>."
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr "<a href=\"/about\">Bu hizmet hakkında</a> daha fazlasını öğrenin."
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr "Haberler:"
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Başlangıçtan itibaren "
|
||||
"üç ay✨</a> (12 Eylül 2019)"
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr "v{{ version }} dayandığı sürüm"
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr "<a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>desteklidir"
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
"Arkaplan resmi <a href=\"https://www.toptal.com/designers/subtlepatterns/"
|
||||
"subtle-grey/\">Subtle Patterns</a>tarafından CC BY-SA 3.0 lisansıyla "
|
||||
"sağlanmıştır."
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr "Bakım Modu"
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr "Anahtarınızı yönetin"
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr "Anahtarınız için doğrulanmış bir e-posta adresi girin"
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr "Bağlantıyı gönder"
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
msgid ""
|
||||
"We will send you an email with a link you can use to remove any of your "
|
||||
"email addresses from search."
|
||||
msgstr ""
|
||||
"E-posta adreslerinizi aramadan kaldırabilmek için kullanabileceğiniz bir "
|
||||
"bağlantıyı size e-posta olarak göndereceğiz."
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
msgid ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
|
||||
"\">{{ key_fpr }}</a></span> anahtarı yönetiliyor."
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr "Anahtarınız aşağıdaki kimlik bilgileriyle yayınlanıyor:"
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr "Sil"
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Herhangi bir adreste \"sil\"e tıklamanız durumunda, o adresi anahtardan "
|
||||
"kaldıracaktır. Aramada artık gözükmeyecektir. <br />Başka bir adres eklemek "
|
||||
"için anahtarı tekrar <a href=\"/upload\">yükleyin</a> ."
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
msgid ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Anahtarınız kimlik bilgisi içermeden yayınlanıyor. (<a href=\"/about\" "
|
||||
"target=\"_blank\">Bunun anlamı nedir</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"Bir adres eklemek için anahtarı tekrar <a href=\"/upload\">yükleyin</a>."
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
msgid ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
"Ek yönergeleri içeren bir e-postayı <span class=\"email\">{{ address }}</"
|
||||
"span> adresine gönderdik."
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
msgid "This address has already been verified."
|
||||
msgstr "Bu adres zaten doğrulanmış."
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
msgid ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
"<span class=\"fingerprint\">{{ key_fpr }}</span> anahtarınız şimdi <a href="
|
||||
"\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</"
|
||||
"span></a> kimliği için yayınlandı."
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
msgid "Upload your key"
|
||||
msgstr "Anahtarınızı yükleyin"
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr "Yükle"
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
msgid ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
"Daha fazla bilgi için<a target=\"_blank\" href=\"/about\">tanıtım</a> veya "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">kullanma kılavuzuna</a> "
|
||||
"bakabilirsiniz."
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
msgid ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
|
||||
"\">{{ key_fpr }}</a></span> anahtarını yüklediniz."
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr "Bu anahtar feshedildi."
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
msgid ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
|
||||
"mean?</a>)."
|
||||
msgstr ""
|
||||
"Kimlik bilgisi olmayan yayınlandı ve e-posta aramasıyla bulunamaz (<a href="
|
||||
"\"/about\" target=\"_blank\">bunun anlamı nedir?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
"Bu anahtar şimdi aşağıdaki kimlik bilgisiyle yayınlandı (<a href=\"/about\" "
|
||||
"target=\"_blank\">bunun anlamı nedir?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr "Yayınlandı"
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
msgid ""
|
||||
"This key is now published with only non-identity information. (<a href=\"/"
|
||||
"about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"Bu anahtar şimdi sadece kimlik olmayan bilgiyle yayınlandı (<a href=\"/about"
|
||||
"\" target=\"_blank\">bunun anlamı nedir?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
msgid ""
|
||||
"To make the key available for search by email address, you can verify it "
|
||||
"belongs to you:"
|
||||
msgstr ""
|
||||
"Anahtarı e-posta adresiyle bulunabilir yapmak için, size ait olduğunu "
|
||||
"doğrulamanız gerekiyor:"
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr "Doğrulama Bekleniyor"
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid ""
|
||||
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
|
||||
"prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
"<strong>Not:</strong> Bazı sağlayıcılar istenmeyen e-postaları önlemek "
|
||||
"amacıyla e-postaları 15 dakikaya kadar geciktirebiliyor. Lütfen sabırla "
|
||||
"bekleyin."
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
msgid "Send Verification Email"
|
||||
msgstr "Doğrulama E-postasını Gönder"
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
msgid ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Bu anahtar e-posta adresi olarak çözümlenemeyen bir kimlik içeriyor. <br /"
|
||||
">Bu kimlik bilgisi <span class=\"brand\">keys.openpgp.org</span>hizmetinde "
|
||||
"yayınlanamaz. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Neden?"
|
||||
"</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
msgid ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Bu anahtar e-posta adresi olarak çözümlenemeyen {{ count_unparsed }} kimlik "
|
||||
"bilgisi içeriyor. <br />Bu kimlik bilgileri <span class=\"brand\">keys."
|
||||
"openpgp.org</span> hizmtinde yayınlanamaz. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Neden?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
msgid ""
|
||||
"This key contains one revoked identity, which is not published. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Bu anahtar feshedilmiş, yayınlanmayan bir kimlik içeriyor. (<a href=\"/about/"
|
||||
"faq#revoked-uids\" target=\"_blank\">Neden?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
msgid ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"Bu anahtar feshedilmiş, yayınlanmayan {{ count_revoked }} kimlik bilgisi "
|
||||
"içeriyor. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Neden?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr "Anahtarlarınız başarılı bir şekilde yüklendi:"
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
msgid ""
|
||||
"<strong>Note:</strong> To make keys searchable by email address, you must "
|
||||
"upload them individually."
|
||||
msgstr ""
|
||||
"<strong>Not:</strong> Anahtarlarınızı e-posta adresiyle bulunabilir yapmak "
|
||||
"için, her birini ayrı ayrı yüklemeniz gerekiyor."
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr "E-posta adresiniz doğrulanıyor..."
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
msgid ""
|
||||
"If the process doesn't complete after a few seconds, please <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
"Eğer işlem bir kaç saniye içerisinde tamamlanmazsa, lütfen <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr "{{ domain }} üzerindeki anahtarınızı yönetin"
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr "Merhaba,"
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
msgid ""
|
||||
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
"Bu <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: "
|
||||
"#333\">{{domain}}</a> tarafından gönderilen otomatik bir mesajdır. "
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr "Eğer bu mesajı siz talep etmediyseniz, lütfen görmezden gelin."
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr "OpenPGP anahtarı: <tt>{{primary_fp}}</tt>"
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
msgstr ""
|
||||
"Bu anahtarla listelenmiş adresleri yönetmek ve silmek için, lütfen aşağıdaki "
|
||||
"bağlantıya tıklayın:"
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
"Daha fazla bilgiyi <a href=\"{{base_uri}}/about\">{{domain}}/about</a> "
|
||||
"sayfasında bulabilirsiniz."
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr "OpenPGP anahtarları 2019 yılından beri dağıtılıyor"
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr "Bu {{domain}} tarafından gönderilen otomatik bir mesajdır. "
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr "OpenPGP anahtarı: {{primary_fp}}"
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr "Daha fazla bilgiyi {{base_uri}}/about sayfasında bulabilirsiniz."
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr "{{userid}} kimliğini {{domain}} üzerindeki anahtarınız için doğrulayın"
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
msgstr ""
|
||||
"Başkalarının bu anahtarı, \"<a rel=\"nofollow\" href=\"#\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{userid}}</a>\" e-posta adresinizi "
|
||||
"kullanarak bulmasını istiyorsanız lütfen aşağıdaki bağlantıya tıklayın:"
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"{{userid}}\",\n"
|
||||
"please follow the link below:"
|
||||
msgstr ""
|
||||
"Başkalarının bu anahtarı, \"{{userid}}\" e-posta adresinizi kullanarak "
|
||||
"bulmasını istiyorsanız lütfen aşağıdaki bağlantıya gidin:"
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr "Bu bağlantı geçersiz veya süresi geçmiş."
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr "Hatalı adres: {address}"
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr "Anahtarsız adres: {address}"
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr "Bu adres için bir istek kısa bir süre önce zaten gönderilmişti."
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr "Anahtar verisi çözümlemesi başarısız oldu."
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr "Eyvah! Lütfen gizli anahtarınızı yüklemeyin!"
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
msgid "No key uploaded."
|
||||
msgstr "Anahtar yüklenmedi."
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr "Yüklenen anahtar işlenirken bir hata oluştu."
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr "Yükleme oturumunun süresi doldu. Lütfen tekrar deneyin."
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
msgid "Invalid verification link."
|
||||
msgstr "Geçersiz doğrulama bağlantısı."
|
447
po/hagrid/zh_Hans.po
Normal file
447
po/hagrid/zh_Hans.po
Normal file
@@ -0,0 +1,447 @@
|
||||
# Translators:
|
||||
# 德夫 乔 <dszw2000@gmail.com>, 2019
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: hagrid\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
|
||||
"Last-Translator: 德夫 乔 <dszw2000@gmail.com>, 2019\n"
|
||||
"Language-Team: Chinese Simplified (https://www.transifex.com/otf/"
|
||||
"teams/102430/zh-Hans/)\n"
|
||||
"Language: zh-Hans\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: src/mail.rs:107
|
||||
msgctxt "Subject for verification email"
|
||||
msgid "Verify {userid} for your key on {domain}"
|
||||
msgstr "在 {domain} 上验证你的密钥 {userid}"
|
||||
|
||||
#: src/mail.rs:140
|
||||
msgctxt "Subject for manage email"
|
||||
msgid "Manage your key on {domain}"
|
||||
msgstr "在 {domain} 上管理你的密钥"
|
||||
|
||||
#: src/gettext_strings.rs:4
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
#: src/gettext_strings.rs:5
|
||||
msgid "Looks like something went wrong :("
|
||||
msgstr "看上去有些东西出错了 :("
|
||||
|
||||
#: src/gettext_strings.rs:6
|
||||
msgid "Error message: {{ internal_error }}"
|
||||
msgstr "错误信息:{{ internal_error }}"
|
||||
|
||||
#: src/gettext_strings.rs:7
|
||||
msgid "There was an error with your request:"
|
||||
msgstr "你的请求有一个错误:"
|
||||
|
||||
#: src/gettext_strings.rs:8
|
||||
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
|
||||
msgstr "我们找到了询问的地址<span class=\"email\">{{ query }}</span>:"
|
||||
|
||||
#: src/gettext_strings.rs:9
|
||||
msgid ""
|
||||
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
|
||||
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
|
||||
"our <a href=\"/about/usage\">usage guide</a> for details."
|
||||
msgstr ""
|
||||
"<strong>提示:</strong>在你的OpenPGP软件上使用<span class=\"brand\">keys."
|
||||
"openpgp.org</span>会更方便。<br />查阅我们的<a href=\"/about/usage\">用户指南"
|
||||
"</a>获取细节。"
|
||||
|
||||
#: src/gettext_strings.rs:10
|
||||
msgid "debug info"
|
||||
msgstr "调试信息"
|
||||
|
||||
#: src/gettext_strings.rs:11
|
||||
msgid "Search by Email Address / Key ID / Fingerprint"
|
||||
msgstr "通过电子邮件地址 / 密钥ID / 指纹搜索"
|
||||
|
||||
#: src/gettext_strings.rs:12
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
#: src/gettext_strings.rs:13
|
||||
msgid ""
|
||||
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
|
||||
"a> your key."
|
||||
msgstr ""
|
||||
"你也可以<a href=\"/upload\">上传</a>或<a href=\"/manage\">管理</a>你的密钥。"
|
||||
|
||||
#: src/gettext_strings.rs:14
|
||||
msgid "Find out more <a href=\"/about\">about this service</a>."
|
||||
msgstr "找到<a href=\"/about\">关于这项服务</a>的更多信息。"
|
||||
|
||||
#: src/gettext_strings.rs:15
|
||||
msgid "News:"
|
||||
msgstr "新闻:"
|
||||
|
||||
#: src/gettext_strings.rs:16
|
||||
msgid ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
|
||||
"launch ✨</a> (2019-09-12)"
|
||||
msgstr ""
|
||||
"<a href=\"/about/news#2019-09-12-three-months-later\">已成功运行三个月✨</"
|
||||
"a>(2019-09-12)"
|
||||
|
||||
#: src/gettext_strings.rs:17
|
||||
msgid "v{{ version }} built from"
|
||||
msgstr "v{{ version }} built from"
|
||||
|
||||
#: src/gettext_strings.rs:18
|
||||
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
|
||||
msgstr "由<a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>支持"
|
||||
|
||||
#: src/gettext_strings.rs:19
|
||||
msgid ""
|
||||
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
|
||||
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
|
||||
msgstr ""
|
||||
"背景图从<a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-"
|
||||
"grey/\">Subtle Patterns</a>依据CC BY-SA 3.0协议获得"
|
||||
|
||||
#: src/gettext_strings.rs:20
|
||||
msgid "Maintenance Mode"
|
||||
msgstr "修复模式"
|
||||
|
||||
#: src/gettext_strings.rs:21
|
||||
msgid "Manage your key"
|
||||
msgstr "管理你的密钥"
|
||||
|
||||
#: src/gettext_strings.rs:22
|
||||
msgid "Enter any verified email address for your key"
|
||||
msgstr "输入任意关联到你的密钥的电子邮件地址"
|
||||
|
||||
#: src/gettext_strings.rs:23
|
||||
msgid "Send link"
|
||||
msgstr "发送链接"
|
||||
|
||||
#: src/gettext_strings.rs:24
|
||||
msgid ""
|
||||
"We will send you an email with a link you can use to remove any of your "
|
||||
"email addresses from search."
|
||||
msgstr ""
|
||||
"我们将把链接用电子邮件的形式发给你,那条链接可以让你在搜索结果中移除你的任意"
|
||||
"邮箱地址"
|
||||
|
||||
#: src/gettext_strings.rs:25
|
||||
msgid ""
|
||||
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"管理密钥<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
|
||||
"\"_blank\">{{ key_fpr }}</a></span>"
|
||||
|
||||
#: src/gettext_strings.rs:26
|
||||
msgid "Your key is published with the following identity information:"
|
||||
msgstr "你的密钥已经用以下身份发布:"
|
||||
|
||||
#: src/gettext_strings.rs:27
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: src/gettext_strings.rs:28
|
||||
msgid ""
|
||||
"Clicking \"delete\" on any address will remove it from this key. It will no "
|
||||
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
|
||||
"\">upload</a> the key again."
|
||||
msgstr ""
|
||||
"点击“删除”将会在密钥中删除该地址的关联。之后该地址将不会在搜索结果中出现。"
|
||||
"<br />如果需要添加另外的地址,请<a href=\"/upload\">重新上传此密钥</a>"
|
||||
|
||||
#: src/gettext_strings.rs:29
|
||||
msgid ""
|
||||
"Your key is published as only non-identity information. (<a href=\"/about\" "
|
||||
"target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"你的密钥以无身份的形式发布。(<a href=\"/about\" target=\"_blank\">这意味着什"
|
||||
"么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:30
|
||||
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
|
||||
msgstr "如果要添加地址,<a href=\"/upload\">重新上传此密钥</a>"
|
||||
|
||||
#: src/gettext_strings.rs:31
|
||||
msgid ""
|
||||
"We have sent an email with further instructions to <span class=\"email"
|
||||
"\">{{ address }}</span>."
|
||||
msgstr ""
|
||||
"我们已经向<span class=\"email\">{{ address }}</span>发送了包含后续说明的电子"
|
||||
"邮件。"
|
||||
|
||||
#: src/gettext_strings.rs:32
|
||||
msgid "This address has already been verified."
|
||||
msgstr "该地址已经被验证过了。"
|
||||
|
||||
#: src/gettext_strings.rs:33
|
||||
msgid ""
|
||||
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
|
||||
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
|
||||
"\"email\">{{ userid }}</span></a>."
|
||||
msgstr ""
|
||||
"你的密钥<span class=\"fingerprint\">{{ key_fpr }}</span>已经以<a href="
|
||||
"\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</"
|
||||
"span></a>的身份发布"
|
||||
|
||||
#: src/gettext_strings.rs:34
|
||||
msgid "Upload your key"
|
||||
msgstr "上传你的密钥"
|
||||
|
||||
#: src/gettext_strings.rs:35
|
||||
msgid "Upload"
|
||||
msgstr "上传"
|
||||
|
||||
#: src/gettext_strings.rs:36
|
||||
msgid ""
|
||||
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
|
||||
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
|
||||
msgstr ""
|
||||
"想了解更多信息?查阅我们的<a target=\"_blank\" href=\"/about\">介绍</a>和<a "
|
||||
"target=\"_blank\" href=\"/about/usage\">用户指南</a>。"
|
||||
|
||||
#: src/gettext_strings.rs:37
|
||||
msgid ""
|
||||
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
|
||||
"target=\"_blank\">{{ key_fpr }}</a></span>."
|
||||
msgstr ""
|
||||
"你上传了密钥<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
|
||||
"\"_blank\">{{ key_fpr }}</a></span>。"
|
||||
|
||||
#: src/gettext_strings.rs:38
|
||||
msgid "This key is revoked."
|
||||
msgstr "该密钥已经被吊销。"
|
||||
|
||||
#: src/gettext_strings.rs:39
|
||||
msgid ""
|
||||
"It is published without identity information and can't be made available for "
|
||||
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
|
||||
"mean?</a>)."
|
||||
msgstr ""
|
||||
"该密钥以无身份信息的形式发布,不能使用电子邮件地址搜索到(<a href=\"/about\" "
|
||||
"target=\"_blank\">这意味着什么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:40
|
||||
msgid ""
|
||||
"This key is now published with the following identity information (<a href="
|
||||
"\"/about\" target=\"_blank\">what does this mean?</a>):"
|
||||
msgstr ""
|
||||
"该密钥已经用以下身份信息发布(<a href=\"/about\" target=\"_blank\">这意味着什"
|
||||
"么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:41
|
||||
msgid "Published"
|
||||
msgstr "已发布"
|
||||
|
||||
#: src/gettext_strings.rs:42
|
||||
msgid ""
|
||||
"This key is now published with only non-identity information. (<a href=\"/"
|
||||
"about\" target=\"_blank\">What does this mean?</a>)"
|
||||
msgstr ""
|
||||
"你的密钥现在以无身份的形式发布。(<a href=\"/about\" target=\"_blank\">这意味"
|
||||
"着什么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:43
|
||||
msgid ""
|
||||
"To make the key available for search by email address, you can verify it "
|
||||
"belongs to you:"
|
||||
msgstr "要让密钥能通过电子邮件地址搜索到,你可以验证该地址属于你:"
|
||||
|
||||
#: src/gettext_strings.rs:44
|
||||
msgid "Verification Pending"
|
||||
msgstr "等待验证"
|
||||
|
||||
#: src/gettext_strings.rs:45
|
||||
msgid ""
|
||||
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
|
||||
"prevent spam. Please be patient."
|
||||
msgstr ""
|
||||
"<strong>提醒:</strong>部分服务商会将邮件拖延最多15分钟来阻止垃圾邮件。请保持"
|
||||
"耐心。"
|
||||
|
||||
#: src/gettext_strings.rs:46
|
||||
msgid "Send Verification Email"
|
||||
msgstr "发送验证邮件"
|
||||
|
||||
#: src/gettext_strings.rs:47
|
||||
msgid ""
|
||||
"This key contains one identity that could not be parsed as an email address."
|
||||
"<br /> This identity can't be published on <span class=\"brand\">keys."
|
||||
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
|
||||
"\">Why?</a>)"
|
||||
msgstr ""
|
||||
"该密钥包含一个不能解析为邮件地址的身份。 <br />该身份无法在<span class="
|
||||
"\"brand\">keys.openpgp.org</span>上发布。 (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">为什么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:48
|
||||
msgid ""
|
||||
"This key contains {{ count_unparsed }} identities that could not be parsed "
|
||||
"as an email address.<br /> These identities can't be published on <span "
|
||||
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
|
||||
"uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"该密钥包含一个不能解析为邮件地址的身份{{ count_unparsed }} 。 <br />这些身份"
|
||||
"无法在<span class=\"brand\">keys.openpgp.org</span>上发布。 (<a href=\"/"
|
||||
"about/faq#non-email-uids\" target=\"_blank\">为什么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:49
|
||||
msgid ""
|
||||
"This key contains one revoked identity, which is not published. (<a href=\"/"
|
||||
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"该密钥包含一个被吊销的身份,所以没有发布(<a href=\"/about/faq#revoked-uids"
|
||||
"\" target=\"_blank\">为什么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:50
|
||||
msgid ""
|
||||
"This key contains {{ count_revoked }} revoked identities, which are not "
|
||||
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
|
||||
msgstr ""
|
||||
"该密钥包含 {{ count_revoked }}个被吊销的身份,所以没有发布(<a href=\"/about/"
|
||||
"faq#revoked-uids\" target=\"_blank\">为什么?</a>)"
|
||||
|
||||
#: src/gettext_strings.rs:51
|
||||
msgid "Your keys have been successfully uploaded:"
|
||||
msgstr "你的密钥已成功上传"
|
||||
|
||||
#: src/gettext_strings.rs:52
|
||||
msgid ""
|
||||
"<strong>Note:</strong> To make keys searchable by email address, you must "
|
||||
"upload them individually."
|
||||
msgstr ""
|
||||
"<strong>提示:</strong>如果想让一些密钥可以通过邮箱地址搜索到,你必须把这些密"
|
||||
"钥逐个上传。"
|
||||
|
||||
#: src/gettext_strings.rs:53
|
||||
msgid "Verifying your email address…"
|
||||
msgstr "正在验证你的邮箱地址……"
|
||||
|
||||
#: src/gettext_strings.rs:54
|
||||
msgid ""
|
||||
"If the process doesn't complete after a few seconds, please <input type="
|
||||
"\"submit\" class=\"textbutton\" value=\"click here\" />."
|
||||
msgstr ""
|
||||
"如果这项操作在若干秒后仍没有完成,<input type=\"submit\" class=\"textbutton"
|
||||
"\" value=\"click here\" />。"
|
||||
|
||||
#: src/gettext_strings.rs:56
|
||||
msgid "Manage your key on {{domain}}"
|
||||
msgstr "在{{domain}}上管理你的密钥"
|
||||
|
||||
#: src/gettext_strings.rs:58
|
||||
msgid "Hi,"
|
||||
msgstr "你好,"
|
||||
|
||||
#: src/gettext_strings.rs:59
|
||||
msgid ""
|
||||
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
|
||||
"decoration:none; color: #333\">{{domain}}</a>."
|
||||
msgstr ""
|
||||
"这是来自<a href=\"{{base_uri}}\" style=\"text-decoration:none; color: "
|
||||
"#333\">{{domain}}</a>的一条自动信息"
|
||||
|
||||
#: src/gettext_strings.rs:60
|
||||
msgid "If you didn't request this message, please ignore it."
|
||||
msgstr "如果你没有申请过这条信息,请忽略。"
|
||||
|
||||
#: src/gettext_strings.rs:61
|
||||
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
|
||||
msgstr "OpenPGP密钥:<tt>{{primary_fp}}</tt>"
|
||||
|
||||
#: src/gettext_strings.rs:62
|
||||
msgid ""
|
||||
"To manage and delete listed addresses on this key, please follow the link "
|
||||
"below:"
|
||||
msgstr "要管理和删除此密钥上列出的地址,请点击以下链接:"
|
||||
|
||||
#: src/gettext_strings.rs:63
|
||||
msgid ""
|
||||
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
|
||||
"a>."
|
||||
msgstr ""
|
||||
"你可以在以下链接找到更多信息 <a href=\"{{base_uri}}/about\">{{domain}}/"
|
||||
"about</a>。"
|
||||
|
||||
#: src/gettext_strings.rs:64
|
||||
msgid "distributing OpenPGP keys since 2019"
|
||||
msgstr "自2019年起传播 OpenPGP 密钥"
|
||||
|
||||
#: src/gettext_strings.rs:67
|
||||
msgid "This is an automated message from {{domain}}."
|
||||
msgstr "这是一条来自 {{domain}} 的自动信息。"
|
||||
|
||||
#: src/gettext_strings.rs:69
|
||||
msgid "OpenPGP key: {{primary_fp}}"
|
||||
msgstr "OpenPGP密钥:{{primary_fp}}"
|
||||
|
||||
#: src/gettext_strings.rs:71
|
||||
msgid "You can find more info at {{base_uri}}/about"
|
||||
msgstr "你可以在以下链接找到更多信息 {{base_uri}}/about"
|
||||
|
||||
#: src/gettext_strings.rs:74
|
||||
msgid "Verify {{userid}} for your key on {{domain}}"
|
||||
msgstr "在 {{domain}} 上验证你的密钥 {{userid}}"
|
||||
|
||||
#: src/gettext_strings.rs:80
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"<a rel=\"nofollow\" "
|
||||
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
|
||||
"please click the link below:"
|
||||
msgstr ""
|
||||
"要让其他人能通过你的邮件地址找到此密钥\"<a rel=\"nofollow\" href=\"#\" style="
|
||||
"\"text-decoration:none; color: #333\">{{userid}}</a>\",请点击下方的链接:"
|
||||
|
||||
#: src/gettext_strings.rs:88
|
||||
msgid ""
|
||||
"To let others find this key from your email address \"{{userid}}\",\n"
|
||||
"please follow the link below:"
|
||||
msgstr ""
|
||||
"要让其他人能通过你的邮件地址找到此密钥\"{{userid}}\",\n"
|
||||
"请点击下方的链接:"
|
||||
|
||||
#: src/web/manage.rs:103
|
||||
msgid "This link is invalid or expired"
|
||||
msgstr "此链接无效或者已经过期"
|
||||
|
||||
#: src/web/manage.rs:129
|
||||
msgid "Malformed address: {address}"
|
||||
msgstr "地址格式错误:{address}"
|
||||
|
||||
#: src/web/manage.rs:136
|
||||
msgid "No key for address: {address}"
|
||||
msgstr "此地址下没有密钥:{address}"
|
||||
|
||||
#: src/web/manage.rs:152
|
||||
msgid "A request has already been sent for this address recently."
|
||||
msgstr "对此地址的申请已经发送"
|
||||
|
||||
#: src/web/vks.rs:112
|
||||
msgid "Parsing of key data failed."
|
||||
msgstr "密钥数据解析失败。"
|
||||
|
||||
#: src/web/vks.rs:121
|
||||
msgid "Whoops, please don't upload secret keys!"
|
||||
msgstr "我的天!请不要上传私钥!"
|
||||
|
||||
#: src/web/vks.rs:134
|
||||
msgid "No key uploaded."
|
||||
msgstr "没有密钥被上传"
|
||||
|
||||
#: src/web/vks.rs:178
|
||||
msgid "Error processing uploaded key."
|
||||
msgstr "处理已上传的密钥时出现问题"
|
||||
|
||||
#: src/web/vks.rs:248
|
||||
msgid "Upload session expired. Please try again."
|
||||
msgstr "上传会话过期,请重试。"
|
||||
|
||||
#: src/web/vks.rs:285
|
||||
msgid "Invalid verification link."
|
||||
msgstr "无效的验证链接"
|
90
src/anonymize_utils.rs
Normal file
90
src/anonymize_utils.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::database::types::Email;
|
||||
|
||||
// from https://github.com/mailcheck/mailcheck/wiki/List-of-Popular-Domains
|
||||
lazy_static! {
|
||||
static ref POPULAR_DOMAINS: HashSet<&'static str> = vec!(
|
||||
/* Default domains included */
|
||||
"aol.com", "att.net", "comcast.net", "facebook.com", "gmail.com", "gmx.com", "googlemail.com",
|
||||
"google.com", "hotmail.com", "hotmail.co.uk", "mac.com", "me.com", "mail.com", "msn.com",
|
||||
"live.com", "sbcglobal.net", "verizon.net", "yahoo.com", "yahoo.co.uk",
|
||||
|
||||
/* Other global domains */
|
||||
"email.com", "fastmail.fm", "games.com" /* AOL */, "gmx.net", "hush.com", "hushmail.com", "icloud.com",
|
||||
"iname.com", "inbox.com", "lavabit.com", "love.com" /* AOL */, "mailbox.org", "posteo.de", "outlook.com", "pobox.com", "protonmail.ch", "protonmail.com", "tutanota.de", "tutanota.com", "tutamail.com", "tuta.io",
|
||||
"keemail.me", "rocketmail.com" /* Yahoo */, "safe-mail.net", "wow.com" /* AOL */, "ygm.com" /* AOL */,
|
||||
"ymail.com" /* Yahoo */, "zoho.com", "yandex.com",
|
||||
|
||||
/* United States ISP domains */
|
||||
"bellsouth.net", "charter.net", "cox.net", "earthlink.net", "juno.com",
|
||||
|
||||
/* British ISP domains */
|
||||
"btinternet.com", "virginmedia.com", "blueyonder.co.uk", "freeserve.co.uk", "live.co.uk",
|
||||
"ntlworld.com", "o2.co.uk", "orange.net", "sky.com", "talktalk.co.uk", "tiscali.co.uk",
|
||||
"virgin.net", "wanadoo.co.uk", "bt.com",
|
||||
|
||||
/* Domains used in Asia */
|
||||
"sina.com", "sina.cn", "qq.com", "naver.com", "hanmail.net", "daum.net", "nate.com", "yahoo.co.jp", "yahoo.co.kr", "yahoo.co.id", "yahoo.co.in", "yahoo.com.sg", "yahoo.com.ph", "163.com", "yeah.net", "126.com", "21cn.com", "aliyun.com", "foxmail.com",
|
||||
|
||||
/* French ISP domains */
|
||||
"hotmail.fr", "live.fr", "laposte.net", "yahoo.fr", "wanadoo.fr", "orange.fr", "gmx.fr", "sfr.fr", "neuf.fr", "free.fr",
|
||||
|
||||
/* German ISP domains */
|
||||
"gmx.de", "hotmail.de", "live.de", "online.de", "t-online.de" /* T-Mobile */, "web.de", "yahoo.de",
|
||||
|
||||
/* Italian ISP domains */
|
||||
"libero.it", "virgilio.it", "hotmail.it", "aol.it", "tiscali.it", "alice.it", "live.it", "yahoo.it", "email.it", "tin.it", "poste.it", "teletu.it",
|
||||
|
||||
/* Russian ISP domains */
|
||||
"mail.ru", "rambler.ru", "yandex.ru", "ya.ru", "list.ru",
|
||||
|
||||
/* Belgian ISP domains */
|
||||
"hotmail.be", "live.be", "skynet.be", "voo.be", "tvcablenet.be", "telenet.be",
|
||||
|
||||
/* Argentinian ISP domains */
|
||||
"hotmail.com.ar", "live.com.ar", "yahoo.com.ar", "fibertel.com.ar", "speedy.com.ar", "arnet.com.ar",
|
||||
|
||||
/* Domains used in Mexico */
|
||||
"yahoo.com.mx", "live.com.mx", "hotmail.es", "hotmail.com.mx", "prodigy.net.mx",
|
||||
|
||||
/* Domains used in Brazil */
|
||||
"yahoo.com.br", "hotmail.com.br", "outlook.com.br", "uol.com.br", "bol.com.br", "terra.com.br", "ig.com.br", "itelefonica.com.br", "r7.com", "zipmail.com.br", "globo.com", "globomail.com", "oi.com.br"
|
||||
).into_iter().collect();
|
||||
}
|
||||
|
||||
pub fn anonymize_address(email: &Email) -> Option<String> {
|
||||
email.as_str()
|
||||
.rsplit('@')
|
||||
.next()
|
||||
.map(|domain| domain.to_lowercase())
|
||||
.and_then(|domain| {
|
||||
if POPULAR_DOMAINS.contains(&domain.as_str()) {
|
||||
Some(domain)
|
||||
} else {
|
||||
domain.rsplit('.').next().map(|tld| tld.to_owned())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn anonymize_address_fallback(email: &Email) -> String {
|
||||
anonymize_address(email).unwrap_or_else(|| "unknown".to_owned())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn known_domain() {
|
||||
let email = "user@hotmail.be".parse::<Email>().unwrap();
|
||||
assert_eq!("hotmail.be", anonymize_address(&email).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_domain() {
|
||||
let email = "user@example.org".parse::<Email>().unwrap();
|
||||
assert_eq!("org", anonymize_address(&email).unwrap());
|
||||
}
|
||||
}
|
67
src/counters.rs
Normal file
67
src/counters.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use lazy_static::lazy_static;
|
||||
use rocket_prometheus::prometheus;
|
||||
|
||||
use crate::anonymize_utils;
|
||||
|
||||
use crate::database::types::Email;
|
||||
|
||||
lazy_static! {
|
||||
static ref KEY_UPLOAD: LabelCounter =
|
||||
LabelCounter::new("hagrid_key_upload", "Uploaded keys", &["result"]);
|
||||
|
||||
static ref MAIL_SENT: LabelCounter =
|
||||
LabelCounter::new("hagrid_mail_sent", "Sent verification mails", &["type", "domain"]);
|
||||
|
||||
static ref KEY_ADDRESS_PUBLISHED: LabelCounter =
|
||||
LabelCounter::new("hagrid_key_address_published", "Verified email addresses", &["domain"]);
|
||||
static ref KEY_ADDRESS_UNPUBLISHED: LabelCounter =
|
||||
LabelCounter::new("hagrid_key_address_unpublished", "Unpublished email addresses", &["domain"]);
|
||||
}
|
||||
|
||||
pub fn register_counters(registry: &prometheus::Registry) {
|
||||
KEY_UPLOAD.register(registry);
|
||||
|
||||
MAIL_SENT.register(registry);
|
||||
|
||||
KEY_ADDRESS_PUBLISHED.register(registry);
|
||||
KEY_ADDRESS_UNPUBLISHED.register(registry);
|
||||
}
|
||||
|
||||
pub fn inc_key_upload(upload_result: &str) {
|
||||
KEY_UPLOAD.inc(&[upload_result]);
|
||||
}
|
||||
|
||||
pub fn inc_mail_sent(mail_type: &str, email: &Email) {
|
||||
let anonymized_adddress = anonymize_utils::anonymize_address_fallback(email);
|
||||
MAIL_SENT.inc(&[mail_type, &anonymized_adddress]);
|
||||
}
|
||||
|
||||
pub fn inc_address_published(email: &Email) {
|
||||
let anonymized_adddress = anonymize_utils::anonymize_address_fallback(email);
|
||||
KEY_ADDRESS_PUBLISHED.inc(&[&anonymized_adddress]);
|
||||
}
|
||||
|
||||
pub fn inc_address_unpublished(email: &Email) {
|
||||
let anonymized_adddress = anonymize_utils::anonymize_address_fallback(email);
|
||||
KEY_ADDRESS_UNPUBLISHED.inc(&[&anonymized_adddress]);
|
||||
}
|
||||
|
||||
struct LabelCounter {
|
||||
prometheus_counter: prometheus::IntCounterVec,
|
||||
}
|
||||
|
||||
impl LabelCounter {
|
||||
fn new(name: &str, help: &str, labels: &[&str]) -> Self {
|
||||
let opts = prometheus::Opts::new(name, help);
|
||||
let prometheus_counter = prometheus::IntCounterVec::new(opts, labels).unwrap();
|
||||
Self { prometheus_counter }
|
||||
}
|
||||
|
||||
fn register(&self, registry: &prometheus::Registry) {
|
||||
registry.register(Box::new(self.prometheus_counter.clone())).unwrap();
|
||||
}
|
||||
|
||||
fn inc(&self, values: &[&str]) {
|
||||
self.prometheus_counter.with_label_values(values).inc();
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ extern crate structopt;
|
||||
use structopt::StructOpt;
|
||||
|
||||
extern crate hagrid_database as database;
|
||||
use database::{Query, Database, KeyDatabase};
|
||||
use crate::database::{Query, Database, KeyDatabase};
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(
|
||||
|
183
src/dump.rs
183
src/dump.rs
@@ -1,16 +1,18 @@
|
||||
// from https://gitlab.com/sequoia-pgp/dump.sequoia-pgp.org/blob/master/src/dump.rs
|
||||
// plus *very* slight adaptions for seqoia 0.8
|
||||
// from https://gitlab.com/sequoia-pgp/sequoia/blob/master/tool/src/commands/dump.rs
|
||||
|
||||
use std::io::{self, Read};
|
||||
use time;
|
||||
|
||||
use sequoia_openpgp::constants::SymmetricAlgorithm;
|
||||
use sequoia_openpgp::conversions::hex;
|
||||
use sequoia_openpgp::{Packet, Result};
|
||||
use sequoia_openpgp::packet::ctb::CTB;
|
||||
use sequoia_openpgp::packet::{Header, BodyLength, Signature};
|
||||
use sequoia_openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue};
|
||||
use sequoia_openpgp::crypto::{SessionKey, s2k::S2K};
|
||||
use sequoia_openpgp::parse::{map::Map, Parse, PacketParserResult};
|
||||
extern crate sequoia_openpgp as openpgp;
|
||||
use self::openpgp::constants::SymmetricAlgorithm;
|
||||
use self::openpgp::conversions::hex;
|
||||
use self::openpgp::crypto::mpis;
|
||||
use self::openpgp::{Packet, Result};
|
||||
use self::openpgp::packet::ctb::CTB;
|
||||
use self::openpgp::packet::{Header, BodyLength, Signature};
|
||||
use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue};
|
||||
use self::openpgp::crypto::{SessionKey, s2k::S2K};
|
||||
use self::openpgp::parse::{map::Map, Parse, PacketParserResult};
|
||||
|
||||
const TIMEFMT: &'static str = "%Y-%m-%dT%H:%M";
|
||||
|
||||
@@ -24,14 +26,17 @@ pub enum Kind {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub fn dump(input: &mut dyn io::Read, output: &mut dyn io::Write, mpis: bool, hex: bool,
|
||||
sk: Option<&SessionKey>)
|
||||
-> Result<Kind> {
|
||||
pub fn dump<W>(input: &mut dyn io::Read, output: &mut dyn io::Write,
|
||||
mpis: bool, hex: bool, sk: Option<&SessionKey>,
|
||||
width: W)
|
||||
-> Result<Kind>
|
||||
where W: Into<Option<usize>>
|
||||
{
|
||||
let mut ppr
|
||||
= sequoia_openpgp::parse::PacketParserBuilder::from_reader(input)?
|
||||
= self::openpgp::parse::PacketParserBuilder::from_reader(input)?
|
||||
.map(hex).finalize()?;
|
||||
let mut message_encrypted = false;
|
||||
let width = 32 * 4 + 80;
|
||||
let width = width.into().unwrap_or(80);
|
||||
let mut dumper = PacketDumper::new(width, mpis);
|
||||
|
||||
while let PacketParserResult::Some(mut pp) = ppr {
|
||||
@@ -232,7 +237,7 @@ impl PacketDumper {
|
||||
header: Option<&Header>, p: &Packet, map: Option<&Map>,
|
||||
additional_fields: Option<&Vec<String>>)
|
||||
-> Result<()> {
|
||||
use sequoia_openpgp::Packet::*;
|
||||
use self::openpgp::Packet::*;
|
||||
|
||||
if let Some(h) = header {
|
||||
write!(output, "{} CTB, {}: ",
|
||||
@@ -283,40 +288,40 @@ impl PacketDumper {
|
||||
level <= {} and data)", n - 1)?,
|
||||
}
|
||||
if self.mpis {
|
||||
use sequoia_openpgp::crypto::mpis::Signature::*;
|
||||
writeln!(output, "{}", i)?;
|
||||
writeln!(output, "{} Signature:", i)?;
|
||||
|
||||
let ii = format!("{} ", i);
|
||||
match s.mpis() {
|
||||
RSA { s } =>
|
||||
mpis::Signature::RSA { s } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&s.value],
|
||||
&[s.value()],
|
||||
&["s"])?,
|
||||
DSA { r, s } =>
|
||||
mpis::Signature::DSA { r, s } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&r.value, &s.value],
|
||||
&[r.value(), s.value()],
|
||||
&["r", "s"])?,
|
||||
Elgamal { r, s } =>
|
||||
mpis::Signature::Elgamal { r, s } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&r.value, &s.value],
|
||||
&[r.value(), s.value()],
|
||||
&["r", "s"])?,
|
||||
EdDSA { r, s } =>
|
||||
mpis::Signature::EdDSA { r, s } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&r.value, &s.value],
|
||||
&[r.value(), s.value()],
|
||||
&["r", "s"])?,
|
||||
ECDSA { r, s } =>
|
||||
mpis::Signature::ECDSA { r, s } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&r.value, &s.value],
|
||||
&[r.value(), s.value()],
|
||||
&["r", "s"])?,
|
||||
Unknown { mpis, rest } => {
|
||||
mpis::Signature::Unknown { mpis, rest } => {
|
||||
let keys: Vec<String> =
|
||||
(0..mpis.len()).map(
|
||||
|i| format!("mpi{}", i)).collect();
|
||||
self.dump_mpis(
|
||||
output, &ii,
|
||||
&mpis.iter().map(|m| m.value.iter().as_slice())
|
||||
.collect::<Vec<_>>()[..],
|
||||
&mpis.iter().map(|m| {
|
||||
m.value().iter().as_slice()
|
||||
}).collect::<Vec<_>>()[..],
|
||||
&keys.iter().map(|k| k.as_str())
|
||||
.collect::<Vec<_>>()[..],
|
||||
)?;
|
||||
@@ -349,48 +354,48 @@ impl PacketDumper {
|
||||
writeln!(output, "{} Pk size: {} bits", i, bits)?;
|
||||
}
|
||||
if self.mpis {
|
||||
use sequoia_openpgp::crypto::mpis::PublicKey::*;
|
||||
writeln!(output, "{}", i)?;
|
||||
writeln!(output, "{} Public Key:", i)?;
|
||||
|
||||
let ii = format!("{} ", i);
|
||||
match k.mpis() {
|
||||
RSA { e, n } =>
|
||||
mpis::PublicKey::RSA { e, n } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&e.value, &n.value],
|
||||
&[e.value(), n.value()],
|
||||
&["e", "n"])?,
|
||||
DSA { p, q, g, y } =>
|
||||
mpis::PublicKey::DSA { p, q, g, y } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&p.value, &q.value, &g.value,
|
||||
&y.value],
|
||||
&[p.value(), q.value(), g.value(),
|
||||
y.value()],
|
||||
&["p", "q", "g", "y"])?,
|
||||
Elgamal { p, g, y } =>
|
||||
mpis::PublicKey::Elgamal { p, g, y } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&p.value, &g.value, &y.value],
|
||||
&[p.value(), g.value(), y.value()],
|
||||
&["p", "g", "y"])?,
|
||||
EdDSA { curve, q } => {
|
||||
mpis::PublicKey::EdDSA { curve, q } => {
|
||||
writeln!(output, "{} Curve: {}", ii, curve)?;
|
||||
self.dump_mpis(output, &ii, &[&q.value], &["q"])?;
|
||||
self.dump_mpis(output, &ii, &[q.value()], &["q"])?;
|
||||
},
|
||||
ECDSA { curve, q } => {
|
||||
mpis::PublicKey::ECDSA { curve, q } => {
|
||||
writeln!(output, "{} Curve: {}", ii, curve)?;
|
||||
self.dump_mpis(output, &ii, &[&q.value], &["q"])?;
|
||||
self.dump_mpis(output, &ii, &[q.value()], &["q"])?;
|
||||
},
|
||||
ECDH { curve, q, hash, sym } => {
|
||||
mpis::PublicKey::ECDH { curve, q, hash, sym } => {
|
||||
writeln!(output, "{} Curve: {}", ii, curve)?;
|
||||
writeln!(output, "{} Hash algo: {}", ii, hash)?;
|
||||
writeln!(output, "{} Symmetric algo: {}", ii,
|
||||
sym)?;
|
||||
self.dump_mpis(output, &ii, &[&q.value], &["q"])?;
|
||||
self.dump_mpis(output, &ii, &[q.value()], &["q"])?;
|
||||
},
|
||||
Unknown { mpis, rest } => {
|
||||
mpis::PublicKey::Unknown { mpis, rest } => {
|
||||
let keys: Vec<String> =
|
||||
(0..mpis.len()).map(
|
||||
|i| format!("mpi{}", i)).collect();
|
||||
self.dump_mpis(
|
||||
output, &ii,
|
||||
&mpis.iter().map(|m| m.value.iter().as_slice())
|
||||
.collect::<Vec<_>>()[..],
|
||||
&mpis.iter().map(|m| {
|
||||
m.value().iter().as_slice()
|
||||
}).collect::<Vec<_>>()[..],
|
||||
&keys.iter().map(|k| k.as_str())
|
||||
.collect::<Vec<_>>()[..],
|
||||
)?;
|
||||
@@ -400,44 +405,46 @@ impl PacketDumper {
|
||||
}
|
||||
|
||||
if let Some(secrets) = k.secret() {
|
||||
use sequoia_openpgp::crypto::mpis::SecretKey::*;
|
||||
use self::openpgp::packet::key::SecretKey;
|
||||
writeln!(output, "{}", i)?;
|
||||
writeln!(output, "{} Secret Key:", i)?;
|
||||
|
||||
let ii = format!("{} ", i);
|
||||
match secrets {
|
||||
sequoia_openpgp::packet::key::SecretKey::Unencrypted {
|
||||
mpis,
|
||||
} => match mpis {
|
||||
RSA { d, p, q, u } =>
|
||||
SecretKey::Unencrypted(ref u) => match u.mpis()
|
||||
{
|
||||
mpis::SecretKey::RSA { d, p, q, u } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&d.value, &p.value, &q.value,
|
||||
&u.value],
|
||||
&[d.value(), p.value(),
|
||||
q.value(), u.value()],
|
||||
&["d", "p", "q", "u"])?,
|
||||
DSA { x } =>
|
||||
self.dump_mpis(output, &ii, &[&x.value],
|
||||
mpis::SecretKey::DSA { x } =>
|
||||
self.dump_mpis(output, &ii, &[x.value()],
|
||||
&["x"])?,
|
||||
Elgamal { x } =>
|
||||
self.dump_mpis(output, &ii, &[&x.value],
|
||||
mpis::SecretKey::Elgamal { x } =>
|
||||
self.dump_mpis(output, &ii, &[x.value()],
|
||||
&["x"])?,
|
||||
EdDSA { scalar } =>
|
||||
self.dump_mpis(output, &ii, &[&scalar.value],
|
||||
mpis::SecretKey::EdDSA { scalar } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[scalar.value()],
|
||||
&["scalar"])?,
|
||||
ECDSA { scalar } =>
|
||||
self.dump_mpis(output, &ii, &[&scalar.value],
|
||||
mpis::SecretKey::ECDSA { scalar } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[scalar.value()],
|
||||
&["scalar"])?,
|
||||
ECDH { scalar } =>
|
||||
self.dump_mpis(output, &ii, &[&scalar.value],
|
||||
mpis::SecretKey::ECDH { scalar } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[scalar.value()],
|
||||
&["scalar"])?,
|
||||
Unknown { mpis, rest } => {
|
||||
mpis::SecretKey::Unknown { mpis, rest } => {
|
||||
let keys: Vec<String> =
|
||||
(0..mpis.len()).map(
|
||||
|i| format!("mpi{}", i)).collect();
|
||||
self.dump_mpis(
|
||||
output, &ii,
|
||||
&mpis.iter()
|
||||
.map(|m| m.value.iter().as_slice())
|
||||
.collect::<Vec<_>>()[..],
|
||||
&mpis.iter().map(|m| {
|
||||
m.value().iter().as_slice()
|
||||
}).collect::<Vec<_>>()[..],
|
||||
&keys.iter().map(|k| k.as_str())
|
||||
.collect::<Vec<_>>()[..],
|
||||
)?;
|
||||
@@ -446,15 +453,13 @@ impl PacketDumper {
|
||||
&["rest"])?;
|
||||
},
|
||||
},
|
||||
sequoia_openpgp::packet::key::SecretKey::Encrypted {
|
||||
s2k, algorithm, ciphertext,
|
||||
} => {
|
||||
SecretKey::Encrypted(ref e) => {
|
||||
writeln!(output, "{}", i)?;
|
||||
write!(output, "{} S2K: ", ii)?;
|
||||
self.dump_s2k(output, &ii, s2k)?;
|
||||
self.dump_s2k(output, &ii, e.s2k())?;
|
||||
writeln!(output, "{} Sym. algo: {}", ii,
|
||||
algorithm)?;
|
||||
self.dump_mpis(output, &ii, &[&ciphertext[..]],
|
||||
e.algo())?;
|
||||
self.dump_mpis(output, &ii, &[e.ciphertext()],
|
||||
&["ciphertext"])?;
|
||||
},
|
||||
}
|
||||
@@ -474,7 +479,7 @@ impl PacketDumper {
|
||||
},
|
||||
|
||||
UserAttribute(ref u) => {
|
||||
use sequoia_openpgp::packet::user_attribute::{Subpacket, Image};
|
||||
use self::openpgp::packet::user_attribute::{Subpacket, Image};
|
||||
writeln!(output, "User Attribute Packet")?;
|
||||
|
||||
for subpacket in u.subpackets() {
|
||||
@@ -532,32 +537,32 @@ impl PacketDumper {
|
||||
writeln!(output, "{} Recipient: {}", i, p.recipient())?;
|
||||
writeln!(output, "{} Pk algo: {}", i, p.pk_algo())?;
|
||||
if self.mpis {
|
||||
use sequoia_openpgp::crypto::mpis::Ciphertext::*;
|
||||
writeln!(output, "{}", i)?;
|
||||
writeln!(output, "{} Encrypted session key:", i)?;
|
||||
|
||||
let ii = format!("{} ", i);
|
||||
match p.esk() {
|
||||
RSA { c } =>
|
||||
mpis::Ciphertext::RSA { c } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&c.value],
|
||||
&[c.value()],
|
||||
&["c"])?,
|
||||
Elgamal { e, c } =>
|
||||
mpis::Ciphertext::Elgamal { e, c } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&e.value, &c.value],
|
||||
&[e.value(), c.value()],
|
||||
&["e", "c"])?,
|
||||
ECDH { e, key } =>
|
||||
mpis::Ciphertext::ECDH { e, key } =>
|
||||
self.dump_mpis(output, &ii,
|
||||
&[&e.value, key],
|
||||
&[e.value(), key],
|
||||
&["e", "key"])?,
|
||||
Unknown { mpis, rest } => {
|
||||
mpis::Ciphertext::Unknown { mpis, rest } => {
|
||||
let keys: Vec<String> =
|
||||
(0..mpis.len()).map(
|
||||
|i| format!("mpi{}", i)).collect();
|
||||
self.dump_mpis(
|
||||
output, &ii,
|
||||
&mpis.iter().map(|m| m.value.iter().as_slice())
|
||||
.collect::<Vec<_>>()[..],
|
||||
&mpis.iter().map(|m| {
|
||||
m.value().iter().as_slice()
|
||||
}).collect::<Vec<_>>()[..],
|
||||
&keys.iter().map(|k| k.as_str())
|
||||
.collect::<Vec<_>>()[..],
|
||||
)?;
|
||||
@@ -572,7 +577,7 @@ impl PacketDumper {
|
||||
writeln!(output, "Symmetric-key Encrypted Session Key Packet")?;
|
||||
writeln!(output, "{} Version: {}", i, s.version())?;
|
||||
match s {
|
||||
sequoia_openpgp::packet::SKESK::V4(ref s) => {
|
||||
self::openpgp::packet::SKESK::V4(ref s) => {
|
||||
writeln!(output, "{} Symmetric algo: {}", i,
|
||||
s.symmetric_algo())?;
|
||||
write!(output, "{} S2K: ", i)?;
|
||||
@@ -583,7 +588,7 @@ impl PacketDumper {
|
||||
}
|
||||
},
|
||||
|
||||
sequoia_openpgp::packet::SKESK::V5(ref s) => {
|
||||
self::openpgp::packet::SKESK::V5(ref s) => {
|
||||
writeln!(output, "{} Symmetric algo: {}", i,
|
||||
s.symmetric_algo())?;
|
||||
writeln!(output, "{} AEAD: {}", i,
|
||||
@@ -771,11 +776,11 @@ impl PacketDumper {
|
||||
writeln!(output, "{} Hash: {}", i, hash)?;
|
||||
writeln!(output, "{} Salt: {}", i, hex::encode(salt))?;
|
||||
},
|
||||
Iterated { hash, ref salt, .. } => {
|
||||
Iterated { hash, ref salt, hash_bytes } => {
|
||||
writeln!(output, "Iterated")?;
|
||||
writeln!(output, "{} Hash: {}", i, hash)?;
|
||||
writeln!(output, "{} Salt: {}", i, hex::encode(salt))?;
|
||||
// writeln!(output, "{} Iterations: {}", i, iterations)?;
|
||||
writeln!(output, "{} Hash bytes: {}", i, hash_bytes)?;
|
||||
},
|
||||
Private(n) =>
|
||||
writeln!(output, "Private({})", n)?,
|
||||
|
91
src/gettext_strings.rs
Normal file
91
src/gettext_strings.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use gettext_macros::t;
|
||||
|
||||
fn _dummy() {
|
||||
t!("Error");
|
||||
t!("Looks like something went wrong :(");
|
||||
t!("Error message: {{ internal_error }}");
|
||||
t!("There was an error with your request:");
|
||||
t!("We found an entry for <span class=\"email\">{{ query }}</span>:");
|
||||
t!("<strong>Hint:</strong> It's more convenient to use <span class=\"brand\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at our <a href=\"/about/usage\">usage guide</a> for details.");
|
||||
t!("debug info");
|
||||
t!("Search by Email Address / Key ID / Fingerprint");
|
||||
t!("Search");
|
||||
t!("You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</a> your key.");
|
||||
t!("Find out more <a href=\"/about\">about this service</a>.");
|
||||
t!("News:");
|
||||
t!("<a href=\"/about/news#2019-09-12-three-months-later\">Three months after launch ✨</a> (2019-09-12)");
|
||||
t!("v{{ version }} built from");
|
||||
t!("Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>");
|
||||
t!("Background image retrieved from <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0");
|
||||
t!("Maintenance Mode");
|
||||
t!("Manage your key");
|
||||
t!("Enter any verified email address for your key");
|
||||
t!("Send link");
|
||||
t!("We will send you an email with a link you can use to remove any of your email addresses from search.");
|
||||
t!("Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>.");
|
||||
t!("Your key is published with the following identity information:");
|
||||
t!("Delete");
|
||||
t!("Clicking \"delete\" on any address will remove it from this key. It will no longer appear in a search.<br /> To add another address, <a href=\"/upload\">upload</a> the key again.");
|
||||
t!("Your key is published as only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)");
|
||||
t!("To add an address, <a href=\"/upload\">upload</a> the key again.");
|
||||
t!("We have sent an email with further instructions to <span class=\"email\">{{ address }}</span>.");
|
||||
t!("This address has already been verified.");
|
||||
t!("Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</span></a>.");
|
||||
t!("Upload your key");
|
||||
t!("Upload");
|
||||
t!("Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and <a target=\"_blank\" href=\"/about/usage\">usage guide</a>.");
|
||||
t!("You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>.");
|
||||
t!("This key is revoked.");
|
||||
t!("It is published without identity information and can't be made available for search by email address (<a href=\"/about\" target=\"_blank\">what does this mean?</a>).");
|
||||
t!("This key is now published with the following identity information (<a href=\"/about\" target=\"_blank\">what does this mean?</a>):");
|
||||
t!("Published");
|
||||
t!("This key is now published with only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)");
|
||||
t!("To make the key available for search by email address, you can verify it belongs to you:");
|
||||
t!("Verification Pending");
|
||||
t!("<strong>Note:</strong> Some providers delay emails for up to 15 minutes to prevent spam. Please be patient.");
|
||||
t!("Send Verification Email");
|
||||
t!("This key contains one identity that could not be parsed as an email address.<br /> This identity can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)");
|
||||
t!("This key contains {{ count_unparsed }} identities that could not be parsed as an email address.<br /> These identities can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)");
|
||||
t!("This key contains one revoked identity, which is not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)");
|
||||
t!("This key contains {{ count_revoked }} revoked identities, which are not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)");
|
||||
t!("Your keys have been successfully uploaded:");
|
||||
t!("<strong>Note:</strong> To make keys searchable by email address, you must upload them individually.");
|
||||
t!("Verifying your email address…");
|
||||
t!("If the process doesn't complete after a few seconds, please <input type=\"submit\" class=\"textbutton\" value=\"click here\" />.");
|
||||
|
||||
t!("Manage your key on {{domain}}");
|
||||
|
||||
t!("Hi,");
|
||||
t!("This is an automated message from <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: #333\">{{domain}}</a>.");
|
||||
t!("If you didn't request this message, please ignore it.");
|
||||
t!("OpenPGP key: <tt>{{primary_fp}}</tt>");
|
||||
t!("To manage and delete listed addresses on this key, please follow the link below:");
|
||||
t!("You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</a>.");
|
||||
t!("distributing OpenPGP keys since 2019");
|
||||
|
||||
t!("Hi,");
|
||||
t!("This is an automated message from {{domain}}.");
|
||||
t!("If you didn't request this message, please ignore it.");
|
||||
t!("OpenPGP key: {{primary_fp}}");
|
||||
t!("To manage and delete listed addresses on this key, please follow the link below:");
|
||||
t!("You can find more info at {{base_uri}}/about");
|
||||
t!("distributing OpenPGP keys since 2019");
|
||||
|
||||
t!("Verify {{userid}} for your key on {{domain}}");
|
||||
|
||||
t!("Hi,");
|
||||
t!("This is an automated message from <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: #333\">{{domain}}</a>.");
|
||||
t!("If you didn't request this message, please ignore it.");
|
||||
t!("OpenPGP key: <tt>{{primary_fp}}</tt>");
|
||||
t!("To let others find this key from your email address \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", please click the link below:");
|
||||
t!("You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</a>.");
|
||||
t!("distributing OpenPGP keys since 2019");
|
||||
|
||||
t!("Hi,");
|
||||
t!("This is an automated message from {{domain}}.");
|
||||
t!("If you didn't request this message, please ignore it.");
|
||||
t!("OpenPGP key: {{primary_fp}}");
|
||||
t!("To let others find this key from your email address \"{{userid}}\",\nplease follow the link below:");
|
||||
t!("You can find more info at {{base_uri}}/about");
|
||||
t!("distributing OpenPGP keys since 2019");
|
||||
}
|
102
src/i18n.rs
Normal file
102
src/i18n.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use handlebars::{
|
||||
Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError
|
||||
};
|
||||
|
||||
use std::io;
|
||||
|
||||
|
||||
pub struct I18NHelper {
|
||||
catalogs: Vec<(&'static str, gettext::Catalog)>,
|
||||
}
|
||||
|
||||
impl I18NHelper {
|
||||
pub fn new(catalogs: Vec<(&'static str, gettext::Catalog)>) -> Self {
|
||||
Self { catalogs }
|
||||
}
|
||||
|
||||
pub fn get_catalog(
|
||||
&self,
|
||||
lang: &str,
|
||||
) -> &gettext::Catalog {
|
||||
let (_, ref catalog) = self.catalogs
|
||||
.iter()
|
||||
.find(|(candidate, _)| *candidate == lang)
|
||||
.unwrap_or_else(|| self.catalogs.get(0).unwrap());
|
||||
catalog
|
||||
}
|
||||
|
||||
// Traverse the fallback chain,
|
||||
pub fn lookup<'a>(
|
||||
&'a self,
|
||||
lang: &str,
|
||||
text_id: &'a str,
|
||||
// args: Option<&HashMap<&str, FluentValue>>,
|
||||
) -> &'a str {
|
||||
let catalog = self.get_catalog(lang);
|
||||
catalog.gettext(text_id)
|
||||
// format!("Unknown localization {}", text_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StringOutput {
|
||||
pub s: String,
|
||||
}
|
||||
|
||||
impl Output for StringOutput {
|
||||
fn write(&mut self, seg: &str) -> Result<(), io::Error> {
|
||||
self.s.push_str(seg);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HelperDef for I18NHelper {
|
||||
fn call<'reg: 'rc, 'rc>(
|
||||
&self,
|
||||
h: &Helper<'reg, 'rc>,
|
||||
reg: &'reg Handlebars,
|
||||
context: &'rc Context,
|
||||
rcx: &mut RenderContext<'reg>,
|
||||
out: &mut dyn Output,
|
||||
) -> HelperResult {
|
||||
let id = if let Some(id) = h.param(0) {
|
||||
id
|
||||
} else {
|
||||
return Err(RenderError::new(
|
||||
"{{text}} must have at least one parameter",
|
||||
));
|
||||
};
|
||||
|
||||
let id = if let Some(id) = id.value().as_str() {
|
||||
id
|
||||
} else {
|
||||
return Err(RenderError::new("{{text}} takes an identifier parameter"));
|
||||
};
|
||||
|
||||
let rerender = h
|
||||
.param(1)
|
||||
.and_then(|p| p
|
||||
.path()
|
||||
.map(|v| v == "rerender")
|
||||
).unwrap_or(false);
|
||||
|
||||
let lang = context
|
||||
.data()
|
||||
.get("lang")
|
||||
.expect("Language not set in context")
|
||||
.as_str()
|
||||
.expect("Language must be string");
|
||||
|
||||
let response = self.lookup(lang, &id);
|
||||
if rerender {
|
||||
let data = rcx.evaluate(context, ".", false).unwrap();
|
||||
let response = reg.render_template(&response, data)
|
||||
.map_err(RenderError::with)?;
|
||||
out.write(&response).map_err(RenderError::with)?;
|
||||
} else {
|
||||
out.write(&response).map_err(RenderError::with)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
165
src/mail.rs
165
src/mail.rs
@@ -7,13 +7,22 @@ use lettre_email::{Mailbox,EmailBuilder};
|
||||
use url;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use crate::counters;
|
||||
|
||||
use database::types::Email;
|
||||
use Result;
|
||||
use rocket_i18n::I18n;
|
||||
use gettext_macros::i18n;
|
||||
|
||||
use rfc2047::rfc2047_encode;
|
||||
|
||||
use crate::template_helpers;
|
||||
|
||||
use crate::database::types::Email;
|
||||
use crate::Result;
|
||||
|
||||
mod context {
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Verification {
|
||||
pub lang: String,
|
||||
pub primary_fp: String,
|
||||
pub uri: String,
|
||||
pub userid: String,
|
||||
@@ -23,6 +32,16 @@ mod context {
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Manage {
|
||||
pub lang: String,
|
||||
pub primary_fp: String,
|
||||
pub uri: String,
|
||||
pub base_uri: String,
|
||||
pub domain: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Welcome {
|
||||
pub lang: String,
|
||||
pub primary_fp: String,
|
||||
pub uri: String,
|
||||
pub base_uri: String,
|
||||
@@ -44,37 +63,35 @@ 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 = template_helpers::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 {
|
||||
lang: i18n.lang.to_string(),
|
||||
primary_fp: tpk_name,
|
||||
uri: format!("{}/verify/{}", base_uri, token),
|
||||
userid: userid.to_string(),
|
||||
@@ -82,62 +99,119 @@ impl Service {
|
||||
domain: self.domain.clone(),
|
||||
};
|
||||
|
||||
counters::inc_mail_sent("verify", userid);
|
||||
|
||||
self.send(
|
||||
&vec![userid],
|
||||
&format!("Verify {} for your key on {}", userid, self.domain),
|
||||
&i18n!(
|
||||
i18n.catalog,
|
||||
context = "Subject for verification email",
|
||||
"Verify {userid} for your key on {domain}";
|
||||
userid = userid,
|
||||
domain = 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 {
|
||||
lang: i18n.lang.to_string(),
|
||||
primary_fp: tpk_name,
|
||||
uri: format!("{}{}", base_uri, link_path),
|
||||
base_uri: base_uri.to_owned(),
|
||||
domain: self.domain.clone(),
|
||||
};
|
||||
|
||||
counters::inc_mail_sent("manage", recipient);
|
||||
|
||||
self.send(
|
||||
&[recipient],
|
||||
&format!("Manage your key on {}", self.domain),
|
||||
&i18n!(
|
||||
i18n.catalog,
|
||||
context = "Subject for manage email",
|
||||
"Manage your key on {domain}";
|
||||
domain = self.domain
|
||||
),
|
||||
"manage",
|
||||
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)
|
||||
}
|
||||
pub fn send_welcome(
|
||||
&self,
|
||||
i18n: &I18n,
|
||||
base_uri: &str,
|
||||
tpk_name: String,
|
||||
userid: &Email,
|
||||
token: &str
|
||||
) -> Result<()> {
|
||||
let ctx = context::Welcome {
|
||||
lang: i18n.lang.to_string(),
|
||||
primary_fp: tpk_name,
|
||||
uri: format!("{}/upload/{}", base_uri, token),
|
||||
base_uri: base_uri.to_owned(),
|
||||
domain: self.domain.clone(),
|
||||
};
|
||||
|
||||
counters::inc_mail_sent("welcome", userid);
|
||||
|
||||
self.send(
|
||||
&vec![userid],
|
||||
&format!("Your key upload on {domain}", domain = self.domain),
|
||||
"welcome",
|
||||
i18n.lang,
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_template(
|
||||
&self,
|
||||
template: &str,
|
||||
locale: &str,
|
||||
ctx: impl Serialize
|
||||
) -> 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
|
||||
) -> 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"))?,
|
||||
)
|
||||
.subject(rfc2047_encode(subject))
|
||||
.alternative(html, txt)
|
||||
.message_id(format!("<{}@{}>", Uuid::new_v4(), self.domain));
|
||||
|
||||
let email = to.iter().fold(email, |email, to| email.to(to.to_string()));
|
||||
@@ -158,3 +232,4 @@ impl Service {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
29
src/main.rs
29
src/main.rs
@@ -1,42 +1,36 @@
|
||||
#![feature(proc_macro_hygiene, plugin, decl_macro)]
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
extern crate failure;
|
||||
use failure::Fallible as Result;
|
||||
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
|
||||
extern crate time;
|
||||
extern crate url;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
extern crate multipart;
|
||||
#[macro_use]
|
||||
extern crate rocket_contrib;
|
||||
|
||||
extern crate sequoia_openpgp;
|
||||
extern crate handlebars;
|
||||
extern crate lettre;
|
||||
extern crate lettre_email;
|
||||
extern crate tempfile;
|
||||
extern crate uuid;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate regex;
|
||||
|
||||
extern crate ring;
|
||||
|
||||
extern crate hagrid_database as database;
|
||||
|
||||
use gettext_macros::init_i18n;
|
||||
|
||||
init_i18n!("hagrid", en, de, fr, pl, tr, zh_Hans);
|
||||
|
||||
mod mail;
|
||||
mod web;
|
||||
mod anonymize_utils;
|
||||
mod tokens;
|
||||
mod sealed_state;
|
||||
mod rate_limiter;
|
||||
mod dump;
|
||||
mod counters;
|
||||
mod i18n;
|
||||
mod gettext_strings;
|
||||
mod web;
|
||||
mod template_helpers;
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = web::serve() {
|
||||
@@ -47,6 +41,7 @@ fn main() {
|
||||
cause = c;
|
||||
}
|
||||
eprintln!();
|
||||
|
||||
::std::process::exit(2);
|
||||
}
|
||||
}
|
||||
|
82
src/template_helpers.rs
Normal file
82
src/template_helpers.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use handlebars::Handlebars;
|
||||
|
||||
use gettext_macros::include_i18n;
|
||||
|
||||
use crate::Result;
|
||||
use crate::i18n::I18NHelper;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TemplateOverrides(String, HashSet<(String)>);
|
||||
|
||||
impl TemplateOverrides {
|
||||
pub fn load(template_path: &Path, localized_dir: &str) -> Result<Self> {
|
||||
load_localized_template_names(template_path, localized_dir)
|
||||
.map(|vec| Self(localized_dir.to_owned(), vec))
|
||||
}
|
||||
|
||||
pub fn get_template_override(&self, lang: &str, tmpl: &str) -> Option<String> {
|
||||
let template_name = format!("{}/{}/{}", self.0, lang, tmpl);
|
||||
if self.1.contains(&template_name) {
|
||||
println!("{}", &template_name);
|
||||
Some(template_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> Result<HashSet<(String)>> {
|
||||
let language_glob = template_path.join(localized_dir).join("*");
|
||||
glob::glob(language_glob.to_str().expect("valid glob path string"))
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.flat_map(|language_path| {
|
||||
let mut template_glob = language_path.join("**").join("*");
|
||||
template_glob.set_extension("hbs");
|
||||
glob::glob(template_glob.to_str().expect("valid glob path string"))
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(move |path| {
|
||||
// TODO this is a hack
|
||||
let template_name = remove_extension(remove_extension(path.strip_prefix(&template_path)?));
|
||||
Ok(template_name.to_string_lossy().into_owned())
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn load_handlebars(template_dir: PathBuf) -> Result<Handlebars> {
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
let i18ns = include_i18n!();
|
||||
let i18n_helper = I18NHelper::new(i18ns);
|
||||
handlebars.register_helper("text", Box::new(i18n_helper));
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use sealed_state::SealedState;
|
||||
use crate::sealed_state::SealedState;
|
||||
|
||||
use serde_json;
|
||||
use serde::{Serialize,de::DeserializeOwned};
|
||||
use Result;
|
||||
use crate::Result;
|
||||
|
||||
pub trait StatelessSerializable : Serialize + DeserializeOwned {
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use std::io;
|
||||
|
||||
use dump::{self, Kind};
|
||||
use web::MyResponse;
|
||||
use crate::dump::{self, Kind};
|
||||
use crate::web::MyResponse;
|
||||
|
||||
use database::{Database, KeyDatabase, Query};
|
||||
use crate::database::{Database, KeyDatabase, Query};
|
||||
|
||||
#[get("/debug?<q>")]
|
||||
pub fn debug_info(
|
||||
@@ -30,7 +30,9 @@ pub fn debug_info(
|
||||
&mut result,
|
||||
false,
|
||||
false,
|
||||
None);
|
||||
None,
|
||||
32 * 4 + 80,
|
||||
);
|
||||
match dump_result {
|
||||
Ok(Kind::TPK) => {
|
||||
match String::from_utf8(result) {
|
||||
|
102
src/web/hkp.rs
102
src/web/hkp.rs
@@ -5,16 +5,19 @@ use rocket::Outcome;
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::request::{self, Request, FromRequest};
|
||||
use rocket::http::uri::Uri;
|
||||
use rocket_i18n::I18n;
|
||||
|
||||
use database::{Database, Query, KeyDatabase};
|
||||
use database::types::{Email, Fingerprint, KeyID};
|
||||
use crate::database::{Database, Query, KeyDatabase};
|
||||
use crate::database::types::{Email, Fingerprint, KeyID};
|
||||
|
||||
use rate_limiter::RateLimiter;
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
|
||||
use tokens;
|
||||
use crate::tokens;
|
||||
|
||||
use web;
|
||||
use web::{HagridState, RequestOrigin, MyResponse, vks_web};
|
||||
use crate::web;
|
||||
use crate::mail;
|
||||
use crate::web::{HagridState, RequestOrigin, MyResponse, vks_web};
|
||||
use crate::web::vks::response::UploadResponse;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Hkp {
|
||||
@@ -67,7 +70,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for Hkp {
|
||||
let maybe_fpr = Fingerprint::from_str(&search);
|
||||
let maybe_keyid = KeyID::from_str(&search);
|
||||
|
||||
if search.starts_with("0x") && search.len() < 16 && !search.contains('@') {
|
||||
let looks_like_short_key_id = !search.contains('@') &&
|
||||
(search.starts_with("0x") && search.len() < 16 || search.len() == 8);
|
||||
if looks_like_short_key_id {
|
||||
Outcome::Success(Hkp::ShortKeyID {
|
||||
query: search,
|
||||
index: index,
|
||||
@@ -113,10 +118,11 @@ pub fn pks_add_form_data(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
tokens_stateless: rocket::State<tokens::Service>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
cont_type: &ContentType,
|
||||
data: Data,
|
||||
) -> MyResponse {
|
||||
match vks_web::upload_post_form_data(db, tokens_stateless, rate_limiter, cont_type, data) {
|
||||
match vks_web::process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data) {
|
||||
Ok(_) => MyResponse::plain("Ok".into()),
|
||||
Err(err) => MyResponse::ise(err),
|
||||
}
|
||||
@@ -128,21 +134,49 @@ pub fn pks_add_form(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
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::upload_post_form(db, tokens_stateless, rate_limiter, data) {
|
||||
match vks_web::process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, 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, &i18n, key_fpr, primary_uid, token) {
|
||||
format!("Upload successful. This is a new key, a welcome email has been sent.")
|
||||
} else {
|
||||
format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = request_origin.get_base_uri())
|
||||
};
|
||||
MyResponse::plain(msg)
|
||||
}
|
||||
Ok(_) => {
|
||||
let msg = format!("Upload successful. Note that identity information will only be published with verification! see {}/about/usage#gnupg-upload", request_origin.get_base_uri());
|
||||
let msg = format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = request_origin.get_base_uri());
|
||||
MyResponse::plain(msg)
|
||||
}
|
||||
Err(err) => MyResponse::ise(err),
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
i18n, request_origin.get_base_uri(), fpr, &primary_uid, &token).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/pks/lookup")]
|
||||
pub fn pks_lookup(state: rocket::State<HagridState>,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
key: Hkp) -> MyResponse {
|
||||
pub fn pks_lookup(
|
||||
state: rocket::State<HagridState>,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
key: Hkp
|
||||
) -> MyResponse {
|
||||
let (query, index) = match key {
|
||||
Hkp::Fingerprint { fpr, index } =>
|
||||
(Query::ByFingerprint(fpr), index),
|
||||
@@ -166,6 +200,17 @@ pub fn pks_lookup(state: rocket::State<HagridState>,
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/pks/internal/index/<query_string>")]
|
||||
pub fn pks_internal_index(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
query_string: String,
|
||||
) -> MyResponse {
|
||||
match query_string.parse() {
|
||||
Ok(query) => key_to_hkp_index(db, query),
|
||||
Err(_) => MyResponse::bad_request_plain("Invalid search query!")
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_hkp_index(db: rocket::State<KeyDatabase>, query: Query)
|
||||
-> MyResponse {
|
||||
use sequoia_openpgp::RevocationStatus;
|
||||
@@ -178,11 +223,7 @@ fn key_to_hkp_index(db: rocket::State<KeyDatabase>, query: Query)
|
||||
let mut out = String::default();
|
||||
let p = tpk.primary();
|
||||
|
||||
let ctime = tpk
|
||||
.primary_key_signature()
|
||||
.and_then(|x| x.signature_creation_time())
|
||||
.map(|x| format!("{}", x.to_timespec().sec))
|
||||
.unwrap_or_default();
|
||||
let ctime = format!("{}", p.creation_time().to_timespec().sec);
|
||||
let extime = tpk
|
||||
.primary_key_signature()
|
||||
.and_then(|x| x.signature_expiration_time())
|
||||
@@ -258,7 +299,7 @@ mod tests {
|
||||
use sequoia_openpgp::tpk::TPKBuilder;
|
||||
use sequoia_openpgp::serialize::Serialize;
|
||||
|
||||
use web::tests::*;
|
||||
use crate::web::tests::*;
|
||||
|
||||
#[test]
|
||||
fn hkp() {
|
||||
@@ -295,9 +336,9 @@ mod tests {
|
||||
let body = response.body_string().unwrap();
|
||||
eprintln!("response: {}", body);
|
||||
|
||||
// Check that we do not get a confirmation mail.
|
||||
let confirm_mail = pop_mail(filemail_into.as_path()).unwrap();
|
||||
assert!(confirm_mail.is_none());
|
||||
// Check that we get a welcome mail
|
||||
let welcome_mail = pop_mail(filemail_into.as_path()).unwrap();
|
||||
assert!(welcome_mail.is_some());
|
||||
|
||||
// We should not be able to look it up by email address.
|
||||
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
||||
@@ -309,6 +350,16 @@ mod tests {
|
||||
// And check that we can see the human-readable result page.
|
||||
check_hr_responses_by_fingerprint(&client, &tpk, 0);
|
||||
|
||||
// Upload the same key again, make sure the welcome mail is not sent again
|
||||
let response = client.post("/pks/add")
|
||||
.body(post_data.as_bytes())
|
||||
.header(ContentType::Form)
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
let welcome_mail = pop_mail(filemail_into.as_path()).unwrap();
|
||||
assert!(welcome_mail.is_none());
|
||||
|
||||
assert_consistency(client.rocket());
|
||||
}
|
||||
|
||||
@@ -345,8 +396,11 @@ mod tests {
|
||||
.header(ContentType::Form)
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let confirm_mail = pop_mail(filemail_into.as_path()).unwrap();
|
||||
assert!(confirm_mail.is_none());
|
||||
|
||||
// Check that there is no welcome mail (since we uploaded two)
|
||||
let welcome_mail = pop_mail(filemail_into.as_path()).unwrap();
|
||||
assert!(welcome_mail.is_none());
|
||||
|
||||
check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
|
||||
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
|
||||
check_hr_responses_by_fingerprint(&client, &tpk_0, 0);
|
||||
|
@@ -1,12 +1,13 @@
|
||||
use rocket::{Request, Data};
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket::http::Method;
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_i18n::I18n;
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use web::MyResponse;
|
||||
use crate::web::MyResponse;
|
||||
|
||||
pub struct MaintenanceMode {
|
||||
maintenance_file: PathBuf,
|
||||
@@ -18,6 +19,7 @@ mod templates {
|
||||
pub message: String,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
pub lang: String,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +95,15 @@ pub fn maintenance_error_json(message: String) -> MyResponse {
|
||||
}
|
||||
|
||||
#[get("/maintenance/web/<message>")]
|
||||
pub fn maintenance_error_web(message: String) -> MyResponse {
|
||||
pub fn maintenance_error_web(
|
||||
message: String,
|
||||
i18n: I18n,
|
||||
) -> MyResponse {
|
||||
let ctx = templates::MaintenanceMode{
|
||||
message,
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
lang: i18n.lang.to_owned(),
|
||||
};
|
||||
MyResponse::Maintenance(Template::render("maintenance", ctx))
|
||||
}
|
||||
|
@@ -1,15 +1,19 @@
|
||||
use rocket;
|
||||
use rocket::State;
|
||||
use rocket::request::Form;
|
||||
use rocket_i18n::I18n;
|
||||
|
||||
use failure::Fallible as Result;
|
||||
|
||||
use web::{RequestOrigin, MyResponse, templates::General};
|
||||
use web::vks_web;
|
||||
use database::{Database, KeyDatabase, types::Email, types::Fingerprint};
|
||||
use mail;
|
||||
use rate_limiter::RateLimiter;
|
||||
use tokens::{self, StatelessSerializable};
|
||||
use gettext_macros::i18n;
|
||||
|
||||
use crate::web::{RequestOrigin, MyResponse};
|
||||
use crate::web::vks_web;
|
||||
use crate::database::{Database, KeyDatabase, types::Email, types::Fingerprint};
|
||||
use crate::mail;
|
||||
use crate::counters;
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
use crate::tokens::{self, StatelessSerializable};
|
||||
|
||||
#[derive(Debug,Serialize,Deserialize)]
|
||||
struct StatelessVerifyToken {
|
||||
@@ -26,8 +30,6 @@ mod templates {
|
||||
pub base_uri: String,
|
||||
pub uid_status: Vec<ManageKeyUidStatus>,
|
||||
pub token: String,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -57,17 +59,18 @@ pub mod forms {
|
||||
|
||||
#[get("/manage")]
|
||||
pub fn vks_manage() -> Result<MyResponse> {
|
||||
Ok(MyResponse::ok("manage/manage", General::default()))
|
||||
Ok(MyResponse::ok_bare("manage/manage"))
|
||||
}
|
||||
|
||||
#[get("/manage/<token>")]
|
||||
pub fn vks_manage_key(
|
||||
request_origin: RequestOrigin,
|
||||
db: State<KeyDatabase>,
|
||||
i18n: I18n,
|
||||
token: String,
|
||||
token_service: rocket::State<tokens::Service>,
|
||||
) -> MyResponse {
|
||||
use database::types::Fingerprint;
|
||||
use crate::database::types::Fingerprint;
|
||||
use std::convert::TryFrom;
|
||||
if let Ok(StatelessVerifyToken { fpr }) = token_service.check(&token) {
|
||||
match db.lookup(&database::Query::ByFingerprint(fpr)) {
|
||||
@@ -92,20 +95,18 @@ pub fn vks_manage_key(
|
||||
uid_status,
|
||||
token,
|
||||
base_uri: request_origin.get_base_uri().to_owned(),
|
||||
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())),
|
||||
Some(i18n!(i18n.catalog, "This link is invalid or expired"))),
|
||||
Err(e) => MyResponse::ise(e),
|
||||
}
|
||||
} else {
|
||||
MyResponse::not_found(
|
||||
Some("manage/manage"),
|
||||
Some("This link is invalid or expired".to_owned()))
|
||||
Some(i18n!(i18n.catalog, "This link is invalid or expired")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +116,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 {
|
||||
@@ -124,14 +126,14 @@ pub fn vks_manage_post(
|
||||
Ok(email) => email,
|
||||
Err(_) => return MyResponse::not_found(
|
||||
Some("manage/manage"),
|
||||
Some(format!("Malformed email address: {}", request.search_term)))
|
||||
Some(i18n!(i18n.catalog, "Malformed address: {address}"; 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))),
|
||||
Some(i18n!(i18n.catalog, "No key for address: {address}"; address = request.search_term))),
|
||||
Err(e) => return MyResponse::ise(e),
|
||||
};
|
||||
|
||||
@@ -140,13 +142,14 @@ pub fn vks_manage_post(
|
||||
.any(|candidate| candidate == email);
|
||||
|
||||
if !email_exists {
|
||||
return MyResponse::ise(failure::err_msg("Address check failed!"));
|
||||
return MyResponse::ise(
|
||||
failure::err_msg("Internal error: address check failed!"));
|
||||
}
|
||||
|
||||
if !rate_limiter.action_perform(format!("manage-{}", &email)) {
|
||||
return MyResponse::not_found(
|
||||
Some("manage/manage"),
|
||||
Some("A request was already sent for this address recently.".to_owned()));
|
||||
Some(i18n!(i18n.catalog, "A request has already been sent for this address recently.")));
|
||||
}
|
||||
|
||||
let fpr: Fingerprint = tpk.fingerprint().try_into().unwrap();
|
||||
@@ -155,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);
|
||||
}
|
||||
|
||||
@@ -169,10 +172,11 @@ pub fn vks_manage_post(
|
||||
pub fn vks_manage_unpublish(
|
||||
request_origin: RequestOrigin,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
i18n: I18n,
|
||||
token_service: rocket::State<tokens::Service>,
|
||||
request: Form<forms::ManageDelete>,
|
||||
) -> MyResponse {
|
||||
match vks_manage_unpublish_or_fail(request_origin, db, token_service, request) {
|
||||
match vks_manage_unpublish_or_fail(request_origin, db, token_service, i18n, request) {
|
||||
Ok(response) => response,
|
||||
Err(e) => MyResponse::ise(e),
|
||||
}
|
||||
@@ -182,10 +186,14 @@ pub fn vks_manage_unpublish_or_fail(
|
||||
request_origin: RequestOrigin,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
token_service: rocket::State<tokens::Service>,
|
||||
i18n: I18n,
|
||||
request: Form<forms::ManageDelete>,
|
||||
) -> Result<MyResponse> {
|
||||
let verify_token = token_service.check::<StatelessVerifyToken>(&request.token)?;
|
||||
let email = request.address.parse::<Email>()?;
|
||||
|
||||
db.set_email_unpublished(&verify_token.fpr, &email)?;
|
||||
Ok(vks_manage_key(request_origin, db, request.token.to_owned(), token_service))
|
||||
counters::inc_address_unpublished(&email);
|
||||
|
||||
Ok(vks_manage_key(request_origin, db, i18n, request.token.to_owned(), token_service))
|
||||
}
|
||||
|
372
src/web/mod.rs
372
src/web/mod.rs
@@ -1,25 +1,33 @@
|
||||
use rocket;
|
||||
use rocket::http::Header;
|
||||
use rocket::http::{Header, Status};
|
||||
use rocket::request;
|
||||
use rocket::outcome::Outcome;
|
||||
use rocket::response::NamedFile;
|
||||
use rocket::response::{NamedFile, Responder, Response};
|
||||
use rocket::config::Config;
|
||||
use rocket_contrib::templates::Template;
|
||||
use rocket_contrib::templates::{Template, Engines};
|
||||
use rocket::http::uri::Uri;
|
||||
use rocket_contrib::json::JsonValue;
|
||||
use rocket::response::status::Custom;
|
||||
use rocket_i18n::I18n;
|
||||
|
||||
use rocket_prometheus::PrometheusMetrics;
|
||||
|
||||
use gettext_macros::{compile_i18n, include_i18n};
|
||||
|
||||
use serde::Serialize;
|
||||
use handlebars::Handlebars;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use mail;
|
||||
use tokens;
|
||||
use rate_limiter::RateLimiter;
|
||||
use crate::mail;
|
||||
use crate::tokens;
|
||||
use crate::counters;
|
||||
use crate::template_helpers::TemplateOverrides;
|
||||
use crate::i18n::I18NHelper;
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
|
||||
use database::{Database, KeyDatabase, Query};
|
||||
use database::types::Fingerprint;
|
||||
use Result;
|
||||
use crate::database::{Database, KeyDatabase, Query};
|
||||
use crate::database::types::Fingerprint;
|
||||
use crate::Result;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
@@ -31,16 +39,37 @@ mod vks_web;
|
||||
mod vks_api;
|
||||
mod debug_web;
|
||||
|
||||
use web::maintenance::MaintenanceMode;
|
||||
use crate::web::maintenance::MaintenanceMode;
|
||||
|
||||
use rocket::http::hyper::header::ContentDisposition;
|
||||
|
||||
pub struct HagridTemplate(&'static str, serde_json::Value);
|
||||
|
||||
impl Responder<'static> for HagridTemplate {
|
||||
fn respond_to(self, req: &rocket::Request) -> std::result::Result<Response<'static>, Status> {
|
||||
let HagridTemplate(tmpl, ctx) = self;
|
||||
let i18n: I18n = req.guard().expect("Error parsing language");
|
||||
let template_overrides: rocket::State<TemplateOverrides> = req.guard().expect("TemplateOverrides must be in managed state");
|
||||
let template_override = template_overrides.get_template_override(i18n.lang, tmpl);
|
||||
let origin: RequestOrigin = req.guard().expect("Error determining request origin");
|
||||
let layout_context = templates::HagridLayout::new(ctx, i18n, origin);
|
||||
|
||||
if let Some(template_override) = template_override {
|
||||
Template::render(template_override, layout_context)
|
||||
} else {
|
||||
Template::render(tmpl, layout_context)
|
||||
}.respond_to(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Responder)]
|
||||
pub enum MyResponse {
|
||||
#[response(status = 200, content_type = "html")]
|
||||
Success(Template),
|
||||
Success(HagridTemplate),
|
||||
#[response(status = 200, content_type = "plain")]
|
||||
Plain(String),
|
||||
#[response(status = 200, content_type = "xml")]
|
||||
Xml(HagridTemplate),
|
||||
#[response(status = 200, content_type = "application/pgp-keys")]
|
||||
Key(String, ContentDisposition),
|
||||
#[response(status = 200, content_type = "application/pgp-keys")]
|
||||
@@ -48,11 +77,11 @@ pub enum MyResponse {
|
||||
#[response(status = 500, content_type = "html")]
|
||||
ServerError(Template),
|
||||
#[response(status = 404, content_type = "html")]
|
||||
NotFound(Template),
|
||||
NotFound(HagridTemplate),
|
||||
#[response(status = 404, content_type = "html")]
|
||||
NotFoundPlain(String),
|
||||
#[response(status = 400, content_type = "html")]
|
||||
BadRequest(Template),
|
||||
BadRequest(HagridTemplate),
|
||||
#[response(status = 400, content_type = "html")]
|
||||
BadRequestPlain(String),
|
||||
#[response(status = 503, content_type = "html")]
|
||||
@@ -64,8 +93,19 @@ pub enum MyResponse {
|
||||
}
|
||||
|
||||
impl MyResponse {
|
||||
pub fn ok<S: Serialize>(tmpl: &'static str, ctx: S) -> Self {
|
||||
MyResponse::Success(Template::render(tmpl, ctx))
|
||||
pub fn ok(tmpl: &'static str, ctx: impl Serialize) -> Self {
|
||||
let context_json = serde_json::to_value(ctx).unwrap();
|
||||
MyResponse::Success(HagridTemplate(tmpl, context_json))
|
||||
}
|
||||
|
||||
pub fn ok_bare(tmpl: &'static str) -> Self {
|
||||
let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap();
|
||||
MyResponse::Success(HagridTemplate(tmpl, context_json))
|
||||
}
|
||||
|
||||
pub fn xml(tmpl: &'static str) -> Self {
|
||||
let context_json = serde_json::to_value(templates::Bare { dummy: () }).unwrap();
|
||||
MyResponse::Xml(HagridTemplate(tmpl, context_json))
|
||||
}
|
||||
|
||||
pub fn plain(s: String) -> Self {
|
||||
@@ -105,21 +145,19 @@ impl MyResponse {
|
||||
|
||||
pub fn ise(e: failure::Error) -> Self {
|
||||
eprintln!("Internal error: {:?}", e);
|
||||
let ctx = templates::FiveHundred{
|
||||
let ctx = templates::FiveHundred {
|
||||
internal_error: e.to_string(),
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
lang: "en".to_string(),
|
||||
};
|
||||
MyResponse::ServerError(Template::render("500", ctx))
|
||||
}
|
||||
|
||||
pub fn bad_request(template: &'static str, e: failure::Error) -> Self {
|
||||
let ctx = templates::General {
|
||||
error: Some(format!("{}", e)),
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
};
|
||||
MyResponse::BadRequest(Template::render(template, ctx))
|
||||
let ctx = templates::Error { error: format!("{}", e) };
|
||||
let context_json = serde_json::to_value(ctx).unwrap();
|
||||
MyResponse::BadRequest(HagridTemplate(template, context_json))
|
||||
}
|
||||
|
||||
pub fn bad_request_plain(message: impl Into<String>) -> Self {
|
||||
@@ -132,64 +170,59 @@ impl MyResponse {
|
||||
|
||||
pub fn not_found(
|
||||
tmpl: Option<&'static str>,
|
||||
message: impl Into<Option<String>>
|
||||
message: impl Into<Option<String>>,
|
||||
) -> Self {
|
||||
MyResponse::NotFound(
|
||||
Template::render(
|
||||
tmpl.unwrap_or("index"),
|
||||
templates::General::new(
|
||||
Some(message.into()
|
||||
.unwrap_or_else(|| "Key not found".to_owned())))))
|
||||
let ctx = templates::Error { error: message.into()
|
||||
.unwrap_or_else(|| "Key not found".to_owned()) };
|
||||
let context_json = serde_json::to_value(ctx).unwrap();
|
||||
MyResponse::NotFound(HagridTemplate(tmpl.unwrap_or("index"), context_json))
|
||||
}
|
||||
}
|
||||
|
||||
mod templates {
|
||||
use super::{I18n, RequestOrigin};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FiveHundred {
|
||||
pub internal_error: String,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
pub lang: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct General {
|
||||
pub struct HagridLayout<T: serde::Serialize> {
|
||||
pub error: Option<String>,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
pub base_uri: String,
|
||||
pub lang: String,
|
||||
pub page: T,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct About {
|
||||
pub base_uri: String,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
pub struct Error {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
impl About {
|
||||
pub fn new(base_uri: impl Into<String>) -> Self {
|
||||
#[derive(Serialize)]
|
||||
pub struct Bare {
|
||||
// Dummy value to make sure {{#with page}} always passes
|
||||
pub dummy: (),
|
||||
}
|
||||
|
||||
impl<T: serde::Serialize> HagridLayout<T> {
|
||||
pub fn new(page: T, i18n: I18n, origin: RequestOrigin) -> Self {
|
||||
Self {
|
||||
base_uri: base_uri.into(),
|
||||
error: None,
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
base_uri: origin.get_base_uri().to_string(),
|
||||
page: page,
|
||||
lang: i18n.lang.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl General {
|
||||
pub fn new(error: Option<String>) -> Self {
|
||||
Self {
|
||||
error: error,
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for General {
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HagridState {
|
||||
@@ -271,61 +304,89 @@ fn files(file: PathBuf, state: rocket::State<HagridState>) -> Option<NamedFile>
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn root() -> Template {
|
||||
Template::render("index", templates::General::default())
|
||||
fn root() -> MyResponse {
|
||||
MyResponse::ok_bare("index")
|
||||
}
|
||||
|
||||
#[get("/about")]
|
||||
fn about() -> Template {
|
||||
Template::render("about/about", templates::General::default())
|
||||
fn about() -> MyResponse {
|
||||
MyResponse::ok_bare("about/about")
|
||||
}
|
||||
|
||||
#[get("/about/news")]
|
||||
fn news() -> Template {
|
||||
Template::render("about/news", templates::General::default())
|
||||
fn news() -> MyResponse {
|
||||
MyResponse::ok_bare("about/news")
|
||||
}
|
||||
|
||||
#[get("/atom.xml")]
|
||||
fn news_atom() -> MyResponse {
|
||||
MyResponse::xml("atom")
|
||||
}
|
||||
|
||||
#[get("/about/faq")]
|
||||
fn faq() -> Template {
|
||||
Template::render("about/faq", templates::General::default())
|
||||
fn faq() -> MyResponse {
|
||||
MyResponse::ok_bare("about/faq")
|
||||
}
|
||||
|
||||
#[get("/about/usage")]
|
||||
fn usage(state: rocket::State<HagridState>) -> Template {
|
||||
Template::render("about/usage", templates::About::new(state.base_uri.clone()))
|
||||
fn usage() -> MyResponse {
|
||||
MyResponse::ok_bare("about/usage")
|
||||
}
|
||||
|
||||
#[get("/about/privacy")]
|
||||
fn privacy() -> Template {
|
||||
Template::render("about/privacy", templates::General::default())
|
||||
fn privacy() -> MyResponse {
|
||||
MyResponse::ok_bare("about/privacy")
|
||||
}
|
||||
|
||||
#[get("/about/api")]
|
||||
fn apidoc() -> Template {
|
||||
Template::render("about/api", templates::General::default())
|
||||
fn apidoc() -> MyResponse {
|
||||
MyResponse::ok_bare("about/api")
|
||||
}
|
||||
|
||||
#[get("/about/stats")]
|
||||
fn stats() -> Template {
|
||||
Template::render("about/stats", templates::General::default())
|
||||
fn stats() -> MyResponse {
|
||||
MyResponse::ok_bare("about/stats")
|
||||
}
|
||||
|
||||
#[get("/errors/<code>/<template>")]
|
||||
fn errors(
|
||||
i18n: I18n,
|
||||
origin: RequestOrigin,
|
||||
code: u16,
|
||||
template: String,
|
||||
) -> Result<Custom<Template>> {
|
||||
if !template.chars().all(|x| x == '-' || char::is_ascii_alphabetic(&x)) {
|
||||
return Err(failure::err_msg("bad request"));
|
||||
}
|
||||
let status_code = Status::from_code(code)
|
||||
.ok_or(failure::err_msg("bad request"))?;
|
||||
let response_body = Template::render(
|
||||
format!("errors/{}-{}", code, template),
|
||||
templates::HagridLayout::new(templates::Bare{dummy: ()}, i18n, origin)
|
||||
);
|
||||
Ok(Custom(status_code, response_body))
|
||||
}
|
||||
|
||||
pub fn serve() -> Result<()> {
|
||||
Err(rocket_factory(rocket::ignite())?.launch().into())
|
||||
}
|
||||
|
||||
fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
||||
compile_i18n!();
|
||||
|
||||
fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
||||
let routes = routes![
|
||||
// infra
|
||||
root,
|
||||
about,
|
||||
news,
|
||||
news_atom,
|
||||
privacy,
|
||||
apidoc,
|
||||
faq,
|
||||
usage,
|
||||
files,
|
||||
stats,
|
||||
errors,
|
||||
// VKSv1
|
||||
vks_api::vks_v1_by_email,
|
||||
vks_api::vks_v1_by_fingerprint,
|
||||
@@ -342,6 +403,7 @@ fn rocket_factory(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
|
||||
@@ -350,6 +412,7 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
||||
hkp::pks_lookup,
|
||||
hkp::pks_add_form,
|
||||
hkp::pks_add_form_data,
|
||||
hkp::pks_internal_index,
|
||||
// EManage
|
||||
manage::vks_manage,
|
||||
manage::vks_manage_key,
|
||||
@@ -368,18 +431,44 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
|
||||
let mail_service = configure_mail_service(rocket.config())?;
|
||||
let rate_limiter = configure_rate_limiter(rocket.config())?;
|
||||
let maintenance_mode = configure_maintenance_mode(rocket.config())?;
|
||||
let localized_template_list = configure_localized_template_list(rocket.config())?;
|
||||
println!("{:?}", localized_template_list);
|
||||
|
||||
Ok(rocket
|
||||
.attach(Template::fairing())
|
||||
let prometheus = configure_prometheus(rocket.config());
|
||||
|
||||
rocket = rocket
|
||||
.attach(Template::custom(|engines: &mut Engines| {
|
||||
let i18ns = include_i18n!();
|
||||
let i18n_helper = I18NHelper::new(i18ns);
|
||||
engines.handlebars.register_helper("text", Box::new(i18n_helper));
|
||||
}))
|
||||
.attach(maintenance_mode)
|
||||
.manage(include_i18n!())
|
||||
.manage(hagrid_state)
|
||||
.manage(stateless_token_service)
|
||||
.manage(stateful_token_service)
|
||||
.manage(mail_service)
|
||||
.manage(db_service)
|
||||
.manage(rate_limiter)
|
||||
.mount("/", routes)
|
||||
)
|
||||
.manage(localized_template_list)
|
||||
.mount("/", routes);
|
||||
|
||||
if let Some(prometheus) = prometheus {
|
||||
rocket = rocket
|
||||
.attach(prometheus.clone())
|
||||
.mount("/metrics", prometheus);
|
||||
}
|
||||
|
||||
Ok(rocket)
|
||||
}
|
||||
|
||||
fn configure_prometheus(config: &Config) -> Option<PrometheusMetrics> {
|
||||
if !config.get_bool("enable_prometheus").unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
let prometheus = PrometheusMetrics::new();
|
||||
counters::register_counters(&prometheus.registry());
|
||||
return Some(prometheus);
|
||||
}
|
||||
|
||||
fn configure_db_service(config: &Config) -> Result<KeyDatabase> {
|
||||
@@ -428,27 +517,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 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 = 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +538,11 @@ fn configure_rate_limiter(config: &Config) -> Result<RateLimiter> {
|
||||
Ok(RateLimiter::new(timeout_secs))
|
||||
}
|
||||
|
||||
fn configure_localized_template_list(config: &Config) -> Result<TemplateOverrides> {
|
||||
let template_dir: PathBuf = config.get_str("template_dir")?.into();
|
||||
TemplateOverrides::load(&template_dir, "localized")
|
||||
}
|
||||
|
||||
fn configure_maintenance_mode(config: &Config) -> Result<MaintenanceMode> {
|
||||
let maintenance_file: PathBuf = config.get_str("maintenance_file")
|
||||
.unwrap_or("maintenance").into();
|
||||
@@ -483,7 +568,7 @@ pub mod tests {
|
||||
use sequoia_openpgp::parse::Parse;
|
||||
use sequoia_openpgp::serialize::Serialize;
|
||||
|
||||
use database::*;
|
||||
use crate::database::*;
|
||||
use super::*;
|
||||
|
||||
// for some reason, this is no longer public in lettre itself
|
||||
@@ -508,7 +593,7 @@ pub mod tests {
|
||||
/// duration of your test. To debug the test, mem::forget it to
|
||||
/// prevent cleanup.
|
||||
pub fn configuration() -> Result<(TempDir, rocket::Config)> {
|
||||
use rocket::config::{Config, Environment};
|
||||
use rocket::config::Environment;
|
||||
|
||||
let root = tempdir()?;
|
||||
let filemail = root.path().join("filemail");
|
||||
@@ -521,6 +606,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())
|
||||
@@ -553,6 +641,22 @@ pub mod tests {
|
||||
db.check_consistency().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn about_translation() {
|
||||
let (_tmpdir, config) = configuration().unwrap();
|
||||
let rocket = rocket_factory(rocket::custom(config)).unwrap();
|
||||
let client = Client::new(rocket).expect("valid rocket instance");
|
||||
|
||||
// Check that we see the landing page.
|
||||
let mut response = client.get("/about")
|
||||
.header(Header::new("Accept-Language", "de"))
|
||||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
||||
// TODO check translation
|
||||
assert!(response.body_string().unwrap().contains("Hagrid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basics() {
|
||||
let (_tmpdir, config) = configuration().unwrap();
|
||||
@@ -593,7 +697,7 @@ pub mod tests {
|
||||
let mut response = client.get("/manage").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
||||
assert!(response.body_string().unwrap().contains("any verified e-mail address"));
|
||||
assert!(response.body_string().unwrap().contains("any verified email address"));
|
||||
|
||||
assert_consistency(client.rocket());
|
||||
}
|
||||
@@ -661,7 +765,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());
|
||||
@@ -691,6 +795,26 @@ 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");
|
||||
let mail_content = pop_mail(&filemail_into).unwrap().unwrap();
|
||||
assert!(mail_content.contains("Dies ist eine automatisierte Nachricht"));
|
||||
assert!(mail_content.contains("Subject: =?utf-8?q?Best=C3=A4tige?= foo@invalid.example.com\r\n\t=?utf-8?q?f=C3=BCr?= deinen =?utf-8?q?Schl=C3=BCssel?= auf local.connection"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upload_two() {
|
||||
let (_tmpdir, config) = configuration().unwrap();
|
||||
@@ -765,7 +889,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.
|
||||
@@ -860,6 +984,20 @@ pub mod tests {
|
||||
check_null_responses_by_email(&client, "foo@invalid.example.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_invalid() {
|
||||
let (_tmpdir, client) = client().unwrap();
|
||||
check_response(&client, "/search?q=0x1234abcd",
|
||||
Status::BadRequest, "not supported, sorry!");
|
||||
check_response(&client, "/search?q=1234abcd",
|
||||
Status::BadRequest, "not supported, sorry!");
|
||||
check_response(&client, "/pks/lookup?op=get&search=0x1234abcd",
|
||||
Status::BadRequest, "not supported, sorry!");
|
||||
check_response(&client, "/pks/lookup?op=get&search=1234abcd",
|
||||
Status::BadRequest, "not supported, sorry!");
|
||||
|
||||
}
|
||||
|
||||
/// Asserts that the given URI 404s.
|
||||
pub fn check_null_response(client: &Client, uri: &str) {
|
||||
let response = client.get(uri).dispatch();
|
||||
@@ -921,6 +1059,25 @@ pub mod tests {
|
||||
assert_eq!(tpk_.userids().count(), nr_uids);
|
||||
}
|
||||
|
||||
// it's a rather "reverse implementation" style test.. can we do better?
|
||||
/// Asserts that the given URI returns a correct hkp "index"
|
||||
/// response for the given TPK.
|
||||
pub fn check_index_response(client: &Client, uri: &str, tpk: &TPK) {
|
||||
let mut response = client.get(uri).dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.content_type(),
|
||||
Some(ContentType::new("text", "plain")));
|
||||
let body = response.body_string().unwrap();
|
||||
|
||||
assert!(body.contains("info:1:1"));
|
||||
let primary_fpr = tpk.fingerprint().to_hex();
|
||||
let algo: u8 = tpk.primary().pk_algo().into();
|
||||
assert!(body.contains(&format!("pub:{}:{}:", primary_fpr, algo)));
|
||||
|
||||
let creation_time = tpk.primary().creation_time().to_timespec().sec;
|
||||
assert!(body.contains(&format!(":{}:", creation_time)));
|
||||
}
|
||||
|
||||
/// Asserts that we can get the given TPK back using the various
|
||||
/// by-fingerprint or by-keyid lookup mechanisms.
|
||||
pub fn check_mr_responses_by_fingerprint(client: &Client, tpk: &TPK,
|
||||
@@ -952,6 +1109,20 @@ pub mod tests {
|
||||
&client,
|
||||
&format!("/pks/lookup?op=get&search=0x{}", keyid),
|
||||
&tpk, nr_uids);
|
||||
|
||||
check_index_response(
|
||||
&client,
|
||||
&format!("/pks/lookup?op=index&search={}", fp),
|
||||
&tpk);
|
||||
}
|
||||
|
||||
/// Asserts that the given URI contains the search string.
|
||||
pub fn check_response(client: &Client, uri: &str, status: Status, needle: &str) {
|
||||
let mut response = client.get(uri).dispatch();
|
||||
assert_eq!(response.status(), status);
|
||||
let body = response.body_string().unwrap();
|
||||
println!("{}", body);
|
||||
assert!(body.contains(needle));
|
||||
}
|
||||
|
||||
/// Asserts that the given URI returns human readable response
|
||||
@@ -980,7 +1151,7 @@ pub mod tests {
|
||||
/// Asserts that the given URI returns human readable response
|
||||
/// page that contains an onion URI pointing to the TPK.
|
||||
pub fn check_hr_response_onion(client: &Client, uri: &str, tpk: &TPK,
|
||||
nr_uids: usize) {
|
||||
_nr_uids: usize) {
|
||||
let mut response = client
|
||||
.get(uri)
|
||||
.header(Header::new("X-Is-Onion", "true"))
|
||||
@@ -1022,7 +1193,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)
|
||||
@@ -1030,6 +1201,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);
|
||||
@@ -1050,8 +1222,12 @@ 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);
|
||||
|
||||
let mut response_second = client.post(&confirm_uri).dispatch();
|
||||
assert_eq!(response_second.status(), Status::BadRequest);
|
||||
assert!(response_second.body_string().unwrap().contains("already been verified"));
|
||||
}
|
||||
|
||||
fn check_mails_and_confirm_deletion(client: &Client, filemail_path: &Path, address: &str) {
|
||||
@@ -1062,7 +1238,6 @@ pub mod tests {
|
||||
|
||||
fn pop_mail_capture_pattern(filemail_path: &Path, pattern: &str) -> String {
|
||||
let mail_content = pop_mail(filemail_path).unwrap().unwrap();
|
||||
println!("{}", mail_content);
|
||||
|
||||
let capture_re = regex::bytes::Regex::new(pattern).unwrap();
|
||||
let capture_content = capture_re.captures(mail_content.as_ref()).unwrap()
|
||||
@@ -1079,7 +1254,8 @@ pub mod tests {
|
||||
let fh = fs::File::open(entry.path())?;
|
||||
fs::remove_file(entry.path())?;
|
||||
let mail: SerializableEmail = ::serde_json::from_reader(fh)?;
|
||||
return Ok(Some(String::from_utf8_lossy(&mail.message).to_string()));
|
||||
let body = String::from_utf8_lossy(&mail.message).to_string();
|
||||
return Ok(Some(body));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
|
111
src/web/vks.rs
111
src/web/vks.rs
@@ -1,11 +1,15 @@
|
||||
use failure::Fallible as Result;
|
||||
|
||||
use database::{Database, KeyDatabase, StatefulTokens, EmailAddressStatus, TpkStatus};
|
||||
use database::types::{Fingerprint,Email};
|
||||
use mail;
|
||||
use tokens::{self, StatelessSerializable};
|
||||
use rate_limiter::RateLimiter;
|
||||
use web::RequestOrigin;
|
||||
use crate::database::{Database, KeyDatabase, StatefulTokens, EmailAddressStatus, TpkStatus, ImportResult};
|
||||
use crate::database::types::{Fingerprint,Email};
|
||||
use crate::mail;
|
||||
use crate::counters;
|
||||
use crate::tokens::{self, StatelessSerializable};
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
use crate::web::RequestOrigin;
|
||||
|
||||
use rocket_i18n::I18n;
|
||||
use gettext_macros::i18n;
|
||||
|
||||
use sequoia_openpgp::TPK;
|
||||
|
||||
@@ -29,6 +33,8 @@ pub mod request {
|
||||
}
|
||||
|
||||
pub mod response {
|
||||
use crate::database::types::Email;
|
||||
|
||||
#[derive(Debug,Serialize,Deserialize,PartialEq,Eq)]
|
||||
pub enum EmailStatus {
|
||||
#[serde(rename = "unpublished")]
|
||||
@@ -50,14 +56,16 @@ pub mod response {
|
||||
is_revoked: bool,
|
||||
status: HashMap<String,EmailStatus>,
|
||||
count_unparsed: usize,
|
||||
is_new_key: bool,
|
||||
primary_uid: Option<Email>,
|
||||
},
|
||||
OkMulti { key_fprs: Vec<String> },
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl UploadResponse {
|
||||
pub fn err(err: &str) -> Self {
|
||||
UploadResponse::Error(err.to_owned())
|
||||
pub fn err(err: impl Into<String>) -> Self {
|
||||
UploadResponse::Error(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +75,8 @@ pub mod response {
|
||||
}
|
||||
|
||||
impl PublishResponse {
|
||||
pub fn err(err: &str) -> Self {
|
||||
PublishResponse::Error(err.to_owned())
|
||||
pub fn err(err: impl Into<String>) -> Self {
|
||||
PublishResponse::Error(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +93,7 @@ impl StatelessSerializable for VerifyTpkState {
|
||||
|
||||
pub fn process_key(
|
||||
db: &KeyDatabase,
|
||||
i18n: &I18n,
|
||||
tokens_stateless: &tokens::Service,
|
||||
rate_limiter: &RateLimiter,
|
||||
reader: impl Read,
|
||||
@@ -100,28 +109,45 @@ pub fn process_key(
|
||||
})
|
||||
{
|
||||
Ok(ppr) => TPKParser::from_packet_parser(ppr),
|
||||
Err(_) => return UploadResponse::err("Failed parsing key"),
|
||||
Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed.")),
|
||||
};
|
||||
let mut tpks = Vec::new();
|
||||
for tpk in parser {
|
||||
tpks.push(match tpk {
|
||||
Ok(t) => {
|
||||
if t.is_tsk() {
|
||||
return UploadResponse::err("Whoops, please don't upload secret keys!");
|
||||
counters::inc_key_upload("secret");
|
||||
return UploadResponse::err(i18n!(
|
||||
i18n.catalog,
|
||||
"Whoops, please don't upload secret keys!"
|
||||
));
|
||||
}
|
||||
t
|
||||
},
|
||||
Err(_) => return UploadResponse::err("No keys uploaded"),
|
||||
Err(_) => {
|
||||
return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed."));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
match tpks.len() {
|
||||
0 => UploadResponse::err("No key submitted"),
|
||||
1 => process_key_single(db, tokens_stateless, rate_limiter, tpks.into_iter().next().unwrap()),
|
||||
0 => UploadResponse::err(i18n!(i18n.catalog, "No key uploaded.")),
|
||||
1 => process_key_single(db, i18n, tokens_stateless, rate_limiter, tpks.into_iter().next().unwrap()),
|
||||
_ => process_key_multiple(db, tpks),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_db_merge(import_result: Result<ImportResult>) -> Result<ImportResult> {
|
||||
match import_result {
|
||||
Ok(ImportResult::New(_)) => counters::inc_key_upload("new"),
|
||||
Ok(ImportResult::Updated(_)) => counters::inc_key_upload("updated"),
|
||||
Ok(ImportResult::Unchanged(_)) => counters::inc_key_upload("unchanged"),
|
||||
Err(_) => counters::inc_key_upload("error"),
|
||||
};
|
||||
|
||||
import_result
|
||||
}
|
||||
|
||||
fn process_key_multiple(
|
||||
db: &KeyDatabase,
|
||||
tpks: Vec<TPK>,
|
||||
@@ -130,7 +156,7 @@ fn process_key_multiple(
|
||||
.into_iter()
|
||||
.flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint())
|
||||
.map(|fpr| (fpr, tpk)))
|
||||
.flat_map(|(fpr, tpk)| db.merge(tpk).map(|_| fpr.to_string()))
|
||||
.flat_map(|(fpr, tpk)| log_db_merge(db.merge(tpk)).map(|_| fpr.to_string()))
|
||||
.collect();
|
||||
|
||||
response::UploadResponse::OkMulti { key_fprs }
|
||||
@@ -138,16 +164,18 @@ fn process_key_multiple(
|
||||
|
||||
fn process_key_single(
|
||||
db: &KeyDatabase,
|
||||
i18n: &I18n,
|
||||
tokens_stateless: &tokens::Service,
|
||||
rate_limiter: &RateLimiter,
|
||||
tpk: TPK,
|
||||
) -> response::UploadResponse {
|
||||
let fp = Fingerprint::try_from(tpk.fingerprint()).unwrap();
|
||||
|
||||
let tpk_status = match db.merge(tpk) {
|
||||
Ok(import_result) => import_result.into_tpk_status(),
|
||||
Err(_) => return UploadResponse::err(&format!(
|
||||
"Something went wrong processing key {}", fp)),
|
||||
let (tpk_status, is_new_key) = match log_db_merge(db.merge(tpk)) {
|
||||
Ok(ImportResult::New(tpk_status)) => (tpk_status, true),
|
||||
Ok(ImportResult::Updated(tpk_status)) => (tpk_status, false),
|
||||
Ok(ImportResult::Unchanged(tpk_status)) => (tpk_status, false),
|
||||
Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Error processing uploaded key.")),
|
||||
};
|
||||
|
||||
let verify_state = {
|
||||
@@ -163,7 +191,7 @@ fn process_key_single(
|
||||
|
||||
let token = tokens_stateless.create(&verify_state);
|
||||
|
||||
show_upload_verify(rate_limiter, token, tpk_status, verify_state)
|
||||
show_upload_verify(rate_limiter, token, tpk_status, verify_state, is_new_key)
|
||||
}
|
||||
|
||||
pub fn request_verify(
|
||||
@@ -173,17 +201,18 @@ 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 {
|
||||
let (verify_state, tpk_status) = match check_tpk_state(&db, &token_stateless, &token) {
|
||||
let (verify_state, tpk_status) = match check_tpk_state(&db, &token_stateless, &i18n, &token) {
|
||||
Ok(ok) => ok,
|
||||
Err(e) => return UploadResponse::err(&e.to_string()),
|
||||
};
|
||||
|
||||
if tpk_status.is_revoked {
|
||||
return show_upload_verify(
|
||||
&rate_limiter, token, tpk_status, verify_state);
|
||||
&rate_limiter, token, tpk_status, verify_state, false);
|
||||
}
|
||||
|
||||
let emails_requested: Vec<_> = addresses.into_iter()
|
||||
@@ -199,21 +228,26 @@ 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show_upload_verify(&rate_limiter, token, tpk_status, verify_state)
|
||||
show_upload_verify(&rate_limiter, token, tpk_status, verify_state, false)
|
||||
}
|
||||
|
||||
fn check_tpk_state(
|
||||
db: &KeyDatabase,
|
||||
token_stateless: &tokens::Service,
|
||||
i18n: &I18n,
|
||||
token: &str,
|
||||
) -> Result<(VerifyTpkState,TpkStatus)> {
|
||||
let verify_state = token_stateless.check::<VerifyTpkState>(token)?;
|
||||
let verify_state = token_stateless.check::<VerifyTpkState>(token)
|
||||
.map_err(|_| failure::err_msg(i18n!(
|
||||
i18n.catalog,
|
||||
"Upload session expired. Please try again."
|
||||
)))?;
|
||||
let tpk_status = db.get_tpk_status(&verify_state.fpr, &verify_state.addresses)?;
|
||||
Ok((verify_state, tpk_status))
|
||||
}
|
||||
@@ -222,6 +256,7 @@ fn send_verify_email(
|
||||
request_origin: &RequestOrigin,
|
||||
mail_service: &mail::Service,
|
||||
token_stateful: &StatefulTokens,
|
||||
i18n: &I18n,
|
||||
fpr: &Fingerprint,
|
||||
email: &Email,
|
||||
) -> Result<()> {
|
||||
@@ -230,6 +265,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,
|
||||
@@ -239,12 +275,14 @@ fn send_verify_email(
|
||||
|
||||
pub fn verify_confirm(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
i18n: &I18n,
|
||||
token_service: rocket::State<StatefulTokens>,
|
||||
token: String,
|
||||
) -> response::PublishResponse {
|
||||
let (fingerprint, email) = match check_publish_token(&db, &token_service, token) {
|
||||
Ok(x) => x,
|
||||
Err(_) => return PublishResponse::err("token verification failed"),
|
||||
Err(_) => return PublishResponse::err(
|
||||
i18n!(i18n.catalog, "Invalid verification link.")),
|
||||
};
|
||||
|
||||
response::PublishResponse::Ok {
|
||||
@@ -260,7 +298,9 @@ fn check_publish_token(
|
||||
) -> Result<(Fingerprint,Email)> {
|
||||
let payload = token_service.pop_token("verify", &token)?;
|
||||
let (fingerprint, email) = serde_json::from_str(&payload)?;
|
||||
|
||||
db.set_email_published(&fingerprint, &email)?;
|
||||
counters::inc_address_published(&email);
|
||||
|
||||
Ok((fingerprint, email))
|
||||
}
|
||||
@@ -270,10 +310,19 @@ fn show_upload_verify(
|
||||
token: String,
|
||||
tpk_status: TpkStatus,
|
||||
verify_state: VerifyTpkState,
|
||||
is_new_key: bool,
|
||||
) -> response::UploadResponse {
|
||||
let key_fpr = verify_state.fpr.to_string();
|
||||
if tpk_status.is_revoked {
|
||||
return response::UploadResponse::Ok { token, key_fpr, count_unparsed: 0, is_revoked: true, status: HashMap::new() };
|
||||
return response::UploadResponse::Ok {
|
||||
token,
|
||||
key_fpr,
|
||||
count_unparsed: 0,
|
||||
is_revoked: true,
|
||||
status: HashMap::new(),
|
||||
is_new_key: false,
|
||||
primary_uid: None,
|
||||
};
|
||||
}
|
||||
|
||||
let status: HashMap<_,_> = tpk_status.email_status
|
||||
@@ -292,8 +341,12 @@ fn show_upload_verify(
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let primary_uid = tpk_status.email_status
|
||||
.get(0)
|
||||
.map(|(email, _)| email)
|
||||
.cloned();
|
||||
|
||||
let count_unparsed = tpk_status.unparsed_uids;
|
||||
|
||||
response::UploadResponse::Ok { token, key_fpr, count_unparsed, is_revoked: false, status }
|
||||
response::UploadResponse::Ok { token, key_fpr, count_unparsed, is_revoked: false, status, is_new_key, primary_uid }
|
||||
}
|
||||
|
@@ -2,27 +2,30 @@ 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 database::{KeyDatabase, StatefulTokens, Query};
|
||||
use database::types::{Email, Fingerprint, KeyID};
|
||||
use mail;
|
||||
use tokens;
|
||||
use rate_limiter::RateLimiter;
|
||||
use crate::database::{KeyDatabase, StatefulTokens, Query};
|
||||
use crate::database::types::{Email, Fingerprint, KeyID};
|
||||
use crate::mail;
|
||||
use crate::tokens;
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
|
||||
use web;
|
||||
use web::{HagridState, RequestOrigin, MyResponse};
|
||||
use web::vks;
|
||||
use web::vks::response::*;
|
||||
use crate::web;
|
||||
use crate::web::{HagridState, RequestOrigin, MyResponse};
|
||||
use crate::web::vks;
|
||||
use crate::web::vks::response::*;
|
||||
|
||||
pub mod json {
|
||||
use web::vks::response::EmailStatus;
|
||||
use crate::web::vks::response::EmailStatus;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VerifyRequest {
|
||||
pub token: String,
|
||||
pub addresses: Vec<String>,
|
||||
pub locale: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -76,12 +79,13 @@ pub fn upload_json(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
tokens_stateless: rocket::State<tokens::Service>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
data: Result<Json<json::UploadRequest>, JsonError>,
|
||||
) -> JsonResult {
|
||||
let data = json_or_error(data)?;
|
||||
use std::io::Cursor;
|
||||
let data_reader = Cursor::new(data.keytext.as_bytes());
|
||||
let result = vks::process_key(&db, &tokens_stateless, &rate_limiter, data_reader);
|
||||
let result = vks::process_key(&db, &i18n, &tokens_stateless, &rate_limiter, data_reader);
|
||||
upload_ok_json(result)
|
||||
}
|
||||
|
||||
@@ -93,9 +97,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 +123,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)
|
||||
}
|
||||
|
||||
@@ -125,7 +145,7 @@ pub fn vks_v1_by_fingerprint(state: rocket::State<HagridState>,
|
||||
fpr: String) -> MyResponse {
|
||||
let query = match fpr.parse::<Fingerprint>() {
|
||||
Ok(fpr) => Query::ByFingerprint(fpr),
|
||||
Err(e) => return MyResponse::bad_request("index", e),
|
||||
Err(_) => return MyResponse::bad_request_plain("malformed fingerprint"),
|
||||
};
|
||||
|
||||
web::key_to_response_plain(state, db, query)
|
||||
@@ -135,9 +155,10 @@ pub fn vks_v1_by_fingerprint(state: rocket::State<HagridState>,
|
||||
pub fn vks_v1_by_email(state: rocket::State<HagridState>,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
email: String) -> MyResponse {
|
||||
let email = email.replace("%40", "@");
|
||||
let query = match email.parse::<Email>() {
|
||||
Ok(email) => Query::ByEmail(email),
|
||||
Err(e) => return MyResponse::bad_request("index", e),
|
||||
Err(_) => return MyResponse::bad_request_plain("malformed e-mail address"),
|
||||
};
|
||||
|
||||
web::key_to_response_plain(state, db, query)
|
||||
@@ -149,7 +170,7 @@ pub fn vks_v1_by_keyid(state: rocket::State<HagridState>,
|
||||
kid: String) -> MyResponse {
|
||||
let query = match kid.parse::<KeyID>() {
|
||||
Ok(keyid) => Query::ByKeyID(keyid),
|
||||
Err(e) => return MyResponse::bad_request("index", e),
|
||||
Err(_) => return MyResponse::bad_request_plain("malformed key id"),
|
||||
};
|
||||
|
||||
web::key_to_response_plain(state, db, query)
|
||||
|
@@ -8,18 +8,20 @@ use multipart::server::Multipart;
|
||||
use rocket::http::ContentType;
|
||||
use rocket::request::Form;
|
||||
use rocket::Data;
|
||||
use rocket_i18n::I18n;
|
||||
use gettext_macros::i18n;
|
||||
|
||||
use database::{KeyDatabase, StatefulTokens, Query, Database};
|
||||
use mail;
|
||||
use tokens;
|
||||
use web::{RequestOrigin, MyResponse};
|
||||
use rate_limiter::RateLimiter;
|
||||
use crate::database::{KeyDatabase, StatefulTokens, Query, Database};
|
||||
use crate::mail;
|
||||
use crate::tokens;
|
||||
use crate::web::{RequestOrigin, MyResponse};
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use web::vks;
|
||||
use web::vks::response::*;
|
||||
use crate::web::vks;
|
||||
use crate::web::vks::response::*;
|
||||
|
||||
const UPLOAD_LIMIT: u64 = 1024 * 1024; // 1 MiB.
|
||||
|
||||
@@ -37,35 +39,26 @@ mod forms {
|
||||
}
|
||||
|
||||
mod template {
|
||||
#[derive(Serialize)]
|
||||
pub struct VerifyForm {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Verify {
|
||||
pub verified: bool,
|
||||
pub key_fpr: String,
|
||||
pub userid: String,
|
||||
pub userid_link: String,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Search {
|
||||
pub query: String,
|
||||
pub fpr: String,
|
||||
pub base_uri: String,
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Upload {
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct VerificationSent {
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
pub key_fpr: String,
|
||||
pub key_link: String,
|
||||
pub is_revoked: bool,
|
||||
@@ -86,8 +79,6 @@ mod template {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UploadOkMultiple {
|
||||
pub commit: String,
|
||||
pub version: String,
|
||||
pub keys: Vec<UploadOkKey>,
|
||||
}
|
||||
|
||||
@@ -105,7 +96,7 @@ impl MyResponse {
|
||||
UploadResponse::Ok { token, .. } => {
|
||||
let uri = uri!(quick_upload_proceed: token);
|
||||
let text = format!(
|
||||
"Key successfully uploaded. Proceed here:\n{}{}\n",
|
||||
"Key successfully uploaded. Proceed with verification here:\n{}{}\n",
|
||||
base_uri,
|
||||
uri
|
||||
);
|
||||
@@ -120,7 +111,7 @@ impl MyResponse {
|
||||
|
||||
fn upload_response(response: UploadResponse) -> Self {
|
||||
match response {
|
||||
UploadResponse::Ok { token, key_fpr, is_revoked, count_unparsed, status } =>
|
||||
UploadResponse::Ok { token, key_fpr, is_revoked, count_unparsed, status, .. } =>
|
||||
Self::upload_ok(token, key_fpr, is_revoked, count_unparsed, status),
|
||||
UploadResponse::OkMulti { key_fprs } =>
|
||||
Self::upload_ok_multi(key_fprs),
|
||||
@@ -161,8 +152,6 @@ impl MyResponse {
|
||||
.sort_unstable_by(|fst,snd| fst.address.cmp(&snd.address));
|
||||
|
||||
let context = template::VerificationSent {
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
is_revoked,
|
||||
key_fpr,
|
||||
key_link,
|
||||
@@ -189,8 +178,6 @@ impl MyResponse {
|
||||
.collect();
|
||||
|
||||
let context = template::UploadOkMultiple {
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
keys,
|
||||
};
|
||||
|
||||
@@ -200,12 +187,7 @@ impl MyResponse {
|
||||
|
||||
#[get("/upload")]
|
||||
pub fn upload() -> MyResponse {
|
||||
let context = template::Upload {
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
};
|
||||
|
||||
MyResponse::ok("upload/upload", context)
|
||||
MyResponse::ok_bare("upload/upload")
|
||||
}
|
||||
|
||||
#[post("/upload/submit", format = "multipart/form-data", data = "<data>")]
|
||||
@@ -213,36 +195,46 @@ pub fn upload_post_form_data(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
tokens_stateless: rocket::State<tokens::Service>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
cont_type: &ContentType,
|
||||
data: Data,
|
||||
) -> Result<MyResponse> {
|
||||
// multipart/form-data
|
||||
let (_, boundary) =
|
||||
match cont_type.params().find(|&(k, _)| k == "boundary") {
|
||||
Some(v) => v,
|
||||
None => return Ok(MyResponse::bad_request(
|
||||
"upload/upload",
|
||||
failure::err_msg("`Content-Type: multipart/form-data` \
|
||||
boundary param not provided"))),
|
||||
};
|
||||
) -> MyResponse {
|
||||
match process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data) {
|
||||
Ok(response) => MyResponse::upload_response(response),
|
||||
Err(err) => MyResponse::bad_request("upload/upload", err),
|
||||
}
|
||||
}
|
||||
|
||||
process_upload(&db, &tokens_stateless, &rate_limiter, data, boundary)
|
||||
pub fn process_post_form_data(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
tokens_stateless: rocket::State<tokens::Service>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
cont_type: &ContentType,
|
||||
data: Data,
|
||||
) -> Result<UploadResponse> {
|
||||
// multipart/form-data
|
||||
let (_, boundary) = cont_type
|
||||
.params()
|
||||
.find(|&(k, _)| k == "boundary")
|
||||
.ok_or_else(|| failure::err_msg("`Content-Type: multipart/form-data` \
|
||||
boundary param not provided"))?;
|
||||
|
||||
process_upload(&db, &tokens_stateless, &rate_limiter, &i18n, data, boundary)
|
||||
}
|
||||
|
||||
#[get("/search?<q>")]
|
||||
pub fn search(
|
||||
request_origin: RequestOrigin,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
q: String,
|
||||
) -> MyResponse {
|
||||
match q.parse::<Query>() {
|
||||
Ok(query) => key_to_response(request_origin, db, q, query),
|
||||
Ok(query) => key_to_response(db, q, query),
|
||||
Err(e) => MyResponse::bad_request("index", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_to_response(
|
||||
request_origin: RequestOrigin,
|
||||
db: rocket::State<KeyDatabase>,
|
||||
query_string: String,
|
||||
query: Query,
|
||||
@@ -255,10 +247,7 @@ fn key_to_response(
|
||||
|
||||
let context = template::Search{
|
||||
query: query_string,
|
||||
base_uri: request_origin.get_base_uri().to_owned(),
|
||||
fpr: fp.to_string(),
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
};
|
||||
|
||||
MyResponse::ok("found", context)
|
||||
@@ -270,6 +259,7 @@ pub fn quick_upload(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
tokens_stateless: rocket::State<tokens::Service>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
request_origin: RequestOrigin,
|
||||
data: Data,
|
||||
) -> MyResponse {
|
||||
@@ -280,11 +270,14 @@ pub fn quick_upload(
|
||||
return MyResponse::bad_request("400-plain", failure::err_msg(error));
|
||||
}
|
||||
|
||||
MyResponse::upload_response_quick(vks::process_key(
|
||||
&db,
|
||||
&tokens_stateless,
|
||||
&rate_limiter,
|
||||
Cursor::new(buf)), request_origin.get_base_uri())
|
||||
MyResponse::upload_response_quick(
|
||||
vks::process_key(
|
||||
&db,
|
||||
&i18n,
|
||||
&tokens_stateless,
|
||||
&rate_limiter,
|
||||
Cursor::new(buf)
|
||||
), request_origin.get_base_uri())
|
||||
}
|
||||
|
||||
#[get("/upload/<token>", rank = 2)]
|
||||
@@ -295,11 +288,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)
|
||||
}
|
||||
|
||||
@@ -309,8 +303,22 @@ pub fn upload_post_form(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
tokens_stateless: rocket::State<tokens::Service>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
data: Data,
|
||||
) -> Result<MyResponse> {
|
||||
) -> MyResponse {
|
||||
match process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data) {
|
||||
Ok(response) => MyResponse::upload_response(response),
|
||||
Err(err) => MyResponse::bad_request("upload/upload", err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_post_form(
|
||||
db: &KeyDatabase,
|
||||
tokens_stateless: &tokens::Service,
|
||||
rate_limiter: &RateLimiter,
|
||||
i18n: &I18n,
|
||||
data: Data,
|
||||
) -> Result<UploadResponse> {
|
||||
use rocket::request::FormItems;
|
||||
use std::io::Cursor;
|
||||
|
||||
@@ -329,19 +337,19 @@ pub fn upload_post_form(
|
||||
|
||||
match key.as_str() {
|
||||
"keytext" => {
|
||||
return Ok(MyResponse::upload_response(vks::process_key(
|
||||
return Ok(vks::process_key(
|
||||
&db,
|
||||
&i18n,
|
||||
&tokens_stateless,
|
||||
&rate_limiter,
|
||||
Cursor::new(decoded_value.as_bytes())
|
||||
)));
|
||||
));
|
||||
}
|
||||
_ => { /* skip */ }
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MyResponse::bad_request("upload/upload",
|
||||
failure::err_msg("No keytext found")))
|
||||
Err(failure::err_msg("No keytext found"))
|
||||
}
|
||||
|
||||
|
||||
@@ -349,18 +357,19 @@ fn process_upload(
|
||||
db: &KeyDatabase,
|
||||
tokens_stateless: &tokens::Service,
|
||||
rate_limiter: &RateLimiter,
|
||||
i18n: &I18n,
|
||||
data: Data,
|
||||
boundary: &str,
|
||||
) -> Result<MyResponse> {
|
||||
) -> Result<UploadResponse> {
|
||||
// saves all fields, any field longer than 10kB goes to a temporary directory
|
||||
// Entries could implement FromData though that would give zero control over
|
||||
// how the files are saved; Multipart would be a good impl candidate though
|
||||
match Multipart::with_body(data.open().take(UPLOAD_LIMIT), boundary).save().temp() {
|
||||
Full(entries) => {
|
||||
process_multipart(db, tokens_stateless, rate_limiter, entries)
|
||||
process_multipart(db, tokens_stateless, rate_limiter, i18n, entries)
|
||||
}
|
||||
Partial(partial, _) => {
|
||||
process_multipart(db, tokens_stateless, rate_limiter, partial.entries)
|
||||
process_multipart(db, tokens_stateless, rate_limiter, i18n, partial.entries)
|
||||
}
|
||||
Error(err) => Err(err.into())
|
||||
}
|
||||
@@ -370,19 +379,16 @@ fn process_multipart(
|
||||
db: &KeyDatabase,
|
||||
tokens_stateless: &tokens::Service,
|
||||
rate_limiter: &RateLimiter,
|
||||
i18n: &I18n,
|
||||
entries: Entries,
|
||||
) -> Result<MyResponse> {
|
||||
) -> Result<UploadResponse> {
|
||||
match entries.fields.get("keytext") {
|
||||
Some(ent) if ent.len() == 1 => {
|
||||
let reader = ent[0].data.readable()?;
|
||||
Ok(MyResponse::upload_response(vks::process_key(db, tokens_stateless, rate_limiter, reader)))
|
||||
Ok(vks::process_key(db, i18n, tokens_stateless, rate_limiter, reader))
|
||||
}
|
||||
Some(_) =>
|
||||
Ok(MyResponse::bad_request(
|
||||
"upload/upload", failure::err_msg("Multiple keytexts found"))),
|
||||
None =>
|
||||
Ok(MyResponse::bad_request(
|
||||
"upload/upload", failure::err_msg("No keytext found"))),
|
||||
Some(_) => Err(failure::err_msg("Multiple keytexts found")),
|
||||
None => Err(failure::err_msg("No keytext found")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,12 +400,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)
|
||||
}
|
||||
|
||||
@@ -411,36 +418,53 @@ 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)
|
||||
}
|
||||
|
||||
#[get("/verify/<token>")]
|
||||
#[post("/verify/<token>")]
|
||||
pub fn verify_confirm(
|
||||
db: rocket::State<KeyDatabase>,
|
||||
token_service: rocket::State<StatefulTokens>,
|
||||
rate_limiter: rocket::State<RateLimiter>,
|
||||
i18n: I18n,
|
||||
token: String,
|
||||
) -> MyResponse {
|
||||
match vks::verify_confirm(db, token_service, token) {
|
||||
let rate_limit_id = format!("verify-token-{}", &token);
|
||||
match vks::verify_confirm(db, &i18n, token_service, token) {
|
||||
PublishResponse::Ok { fingerprint, email } => {
|
||||
rate_limiter.action_perform(rate_limit_id);
|
||||
let userid_link = uri!(search: &email).to_string();
|
||||
let context = template::Verify {
|
||||
verified: true,
|
||||
userid: email,
|
||||
key_fpr: fingerprint,
|
||||
userid_link,
|
||||
version: env!("VERGEN_SEMVER").to_string(),
|
||||
commit: env!("VERGEN_SHA_SHORT").to_string(),
|
||||
};
|
||||
|
||||
MyResponse::ok("upload/publish-result", context)
|
||||
},
|
||||
PublishResponse::Error(error) => MyResponse::plain(error),
|
||||
PublishResponse::Error(error) => {
|
||||
let error_msg = if rate_limiter.action_check(rate_limit_id) {
|
||||
failure::err_msg(error)
|
||||
} else {
|
||||
failure::err_msg(i18n!(i18n.catalog, "This address has already been verified."))
|
||||
};
|
||||
MyResponse::bad_request("400", error_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/verify/<token>")]
|
||||
pub fn verify_confirm_form(
|
||||
token: String,
|
||||
) -> MyResponse {
|
||||
MyResponse::ok("upload/verification-form", template::VerifyForm {
|
||||
token
|
||||
})
|
||||
}
|
||||
|
29
templates-translated/de/about/about.html.hbs
Normal file
29
templates-translated/de/about/about.html.hbs
Normal file
@@ -0,0 +1,29 @@
|
||||
<div class="about">
|
||||
<center><h2>Übersicht | <a href="/about/news">News</a> | <a href="/about/usage">Nutzung</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Statistik</a> | <a href="/about/privacy">Privacy Policy</a>
|
||||
</h2></center>
|
||||
|
||||
<p>Der <tt>keys.openpgp.org</tt> Server ist ein öffentlicher Service für die Verteilung von OpenPGP-Schlüsseln, üblicherweise als "Keyserver" bezeichnet.</p>
|
||||
|
||||
<p><strong>Für Details zur Nutzung, siehe <a href="/about/usage">Nutzungshinweise</a></strong></p>
|
||||
|
||||
<h3>Funktionsweise</h3>
|
||||
|
||||
<p>Ein OpenPGP Schlüssel enthält zwei Arten Information:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Identitäten</strong> beschreiben die Teile des Schlüssels, welche den Besitzer identifizieren. Sie sind auch bekannt als "User IDs", und enhalten typischerweise einen Namen und eine E-Mail-Adresse.</li>
|
||||
<li>Die <strong>Nicht-Identitäts-Informationen</strong> enthalten techische Details über den Schlüssel an sich. Dies sind insbesondere die mathematischen Objekte, mit deren Hilfe Signaturen berechnet und Nachrichten verschlüsselt werden. Auch enthält dies Metadaten des Schlüssels wie den Zeitpunkt der Erstellung, ein eventuelles Ablaufdatum, und den Widerrufsstatus.</li>
|
||||
</ul>
|
||||
<p>Traditionell wurden diese Bestandteile von Keyservern gemeinsam ausgeliefert. Auf <span class="brand">keys.openpgp.org</span> werden sie jedoch getrennt behandelt: Während Nicht-Identität-Informationen jedes Schlüssels frei hochgeladen werden können, werden Identitäts-Informationen nur unter bestimmten Bedingungen gespeichert und weiter veröffentlicht:</p>
|
||||
|
||||
<p>Die <strong>Nicht-Identitäts-Informationen</strong> eines Schlüssels sind rein technischer Natur, und können nicht zur direkten Identifikation von Personen verwendet werden. Sie werden nach Prüfung der kryptografischen Integrität ohne weitere Bestätigung gespeichert und verteilt. Fortgeschrittene OpenPGP-Anwendungen können <span class="brand">keys.openpgp.org</span> verwenden, um diese Informationen in allen bekannten Schlüsseln auf dem neusten Stand zu halten, und eine sichere und zuverlässige Kommunikation sicherzustellen.</p>
|
||||
|
||||
<p>Die <strong>Identitäts-Informationen </strong> eines OpenPGP-Schlüssels enthalten persönliche Daten ihres Besitzers. Sie werden ausschließlich mit dessen Zustimmung veröffentlicht, welche mit einer simplen Bestätigung via E-Mail abgefragt wird. Insbesondere kann ein Schlüssel nur mit Zustimmung anhand seiner E-Mail-Adressen in der Suche gefunden werden.</p>
|
||||
|
||||
<h3 id="community">Community und Plattform</h3>
|
||||
|
||||
<p>Dieser Dienst wird ehrenamtlich unterhalten. Die Betreiber dieses Dienstes kommen aus unterschiedlichen OpenPGP-Projekten, insbesondere Sequoia-PGP, OpenKeychain, und Enigmail. Du kannst uns unter #hagrid im Freenode IRC erreichen, oder #hagrid:stratum0.org auf Matrix. Natürlich sind wir auch per E-Mail verfügbar, unter <tt>support at keys punkt openpgp punkt org</tt>.</p>
|
||||
|
||||
<p>Technisch verwendet <tt>keys.openpgp.org</tt> die <a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a> Keyserver-Software, welche auf <a href="https://sequoia-pgp.org">Sequoia-PGP</a> basiert. Der Dienst wird gehostet auf der <a href="https://eclips.is" target="_blank">eclips.is</a> Plattform, die von <a href="https://greenhost.net/" target="_blank">Greenhost</a> unterhalten wird und sich auf Internet-Freedom-Projekte spezialisiert.</p>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user