2023-05-11 17:34:05 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-05-12 12:51:10 +00:00
|
|
|
use std::collections::HashMap;
|
2023-05-11 17:34:05 +00:00
|
|
|
use std::error::Error;
|
|
|
|
use std::sync;
|
2023-05-11 12:19:36 +00:00
|
|
|
use warp::Filter;
|
|
|
|
|
2023-05-11 17:34:05 +00:00
|
|
|
use crate::domain;
|
|
|
|
|
|
|
|
pub mod http_tpl;
|
2023-05-12 16:17:23 +00:00
|
|
|
mod util;
|
2023-05-11 17:34:05 +00:00
|
|
|
|
|
|
|
type Handlebars<'a> = sync::Arc<handlebars::Handlebars<'a>>;
|
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
struct Renderer<'a, DM>
|
2023-05-12 14:43:28 +00:00
|
|
|
where
|
|
|
|
DM: domain::manager::Manager,
|
|
|
|
{
|
|
|
|
domain_manager: sync::Arc<DM>,
|
2023-05-12 16:17:23 +00:00
|
|
|
target_cname: sync::Arc<domain::Name>,
|
|
|
|
passphrase: sync::Arc<String>,
|
|
|
|
|
2023-05-12 12:51:10 +00:00
|
|
|
handlebars: Handlebars<'a>,
|
|
|
|
query_args: HashMap<String, String>,
|
|
|
|
}
|
|
|
|
|
2023-05-13 13:22:47 +00:00
|
|
|
#[derive(Serialize)]
|
2023-05-13 13:37:24 +00:00
|
|
|
struct BasePresenter<'a, T> {
|
|
|
|
page_name: &'a str,
|
|
|
|
query_args: &'a HashMap<String, String>,
|
|
|
|
data: &'a T,
|
2023-05-13 13:22:47 +00:00
|
|
|
}
|
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
impl<'a, DM> Renderer<'a, DM>
|
2023-05-12 12:51:10 +00:00
|
|
|
where
|
2023-05-12 14:43:28 +00:00
|
|
|
DM: domain::manager::Manager,
|
2023-05-12 12:51:10 +00:00
|
|
|
{
|
2023-05-13 13:37:24 +00:00
|
|
|
// TODO make this use an io::Write, rather than warp::Reply
|
|
|
|
fn render<T>(&self, name: &'_ str, value: &'_ T) -> Box<dyn warp::Reply>
|
|
|
|
where
|
|
|
|
T: Serialize,
|
|
|
|
{
|
|
|
|
let rendered = match self.handlebars.render(name, value) {
|
|
|
|
Ok(res) => res,
|
|
|
|
Err(handlebars::RenderError {
|
|
|
|
template_name: None,
|
|
|
|
..
|
|
|
|
}) => return self.render_error_page(404, "Static asset not found"),
|
|
|
|
Err(err) => {
|
|
|
|
return self.render_error_page(500, format!("template error: {err}").as_str())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let content_type = mime_guess::from_path(name)
|
|
|
|
.first_or_octet_stream()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let reply = warp::reply::html(rendered);
|
|
|
|
|
|
|
|
Box::from(warp::reply::with_header(
|
|
|
|
reply,
|
|
|
|
"Content-Type",
|
|
|
|
content_type,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render_error_page(&self, status_code: u16, e: &'_ str) -> Box<dyn warp::Reply> {
|
|
|
|
#[derive(Serialize)]
|
|
|
|
struct Response<'a> {
|
|
|
|
error_msg: &'a str,
|
|
|
|
}
|
|
|
|
|
|
|
|
Box::from(warp::reply::with_status(
|
|
|
|
self.render(
|
|
|
|
"/base.html",
|
|
|
|
&BasePresenter {
|
|
|
|
page_name: "/error.html",
|
|
|
|
query_args: &HashMap::default(),
|
|
|
|
data: &Response { error_msg: e },
|
|
|
|
},
|
|
|
|
),
|
|
|
|
status_code.try_into().unwrap(),
|
|
|
|
))
|
|
|
|
}
|
2023-05-12 12:51:10 +00:00
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
fn render_page<T>(&self, name: &'_ str, data: &'_ T) -> Box<dyn warp::Reply>
|
|
|
|
where
|
|
|
|
T: Serialize,
|
|
|
|
{
|
|
|
|
self.render(
|
|
|
|
"/base.html",
|
|
|
|
&BasePresenter {
|
|
|
|
page_name: name,
|
|
|
|
query_args: &self.query_args,
|
|
|
|
data,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2023-05-11 17:34:05 +00:00
|
|
|
}
|
|
|
|
|
2023-05-12 14:43:28 +00:00
|
|
|
pub fn new<DM>(
|
|
|
|
manager: DM,
|
2023-05-12 16:17:23 +00:00
|
|
|
target_cname: domain::Name,
|
|
|
|
passphrase: String,
|
2023-05-11 17:34:05 +00:00
|
|
|
) -> Result<
|
2023-05-12 14:43:28 +00:00
|
|
|
impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone + 'static,
|
2023-05-11 17:34:05 +00:00
|
|
|
Box<dyn Error>,
|
2023-05-12 14:43:28 +00:00
|
|
|
>
|
|
|
|
where
|
|
|
|
DM: domain::manager::Manager + 'static,
|
|
|
|
{
|
|
|
|
let manager = sync::Arc::new(manager);
|
2023-05-12 16:17:23 +00:00
|
|
|
let target_cname = sync::Arc::new(target_cname);
|
|
|
|
let passphrase = sync::Arc::new(passphrase);
|
2023-05-12 14:43:28 +00:00
|
|
|
|
2023-05-11 17:34:05 +00:00
|
|
|
let hbs = sync::Arc::new(self::http_tpl::get()?);
|
2023-05-13 13:37:24 +00:00
|
|
|
let with_renderer = warp::any()
|
2023-05-12 12:51:10 +00:00
|
|
|
.and(warp::query::<HashMap<String, String>>())
|
2023-05-13 13:37:24 +00:00
|
|
|
.map(move |query_args: HashMap<String, String>| Renderer {
|
2023-05-12 14:43:28 +00:00
|
|
|
domain_manager: manager.clone(),
|
2023-05-12 16:17:23 +00:00
|
|
|
target_cname: target_cname.clone(),
|
|
|
|
passphrase: passphrase.clone(),
|
2023-05-12 12:51:10 +00:00
|
|
|
handlebars: hbs.clone(),
|
|
|
|
query_args,
|
|
|
|
});
|
2023-05-11 17:34:05 +00:00
|
|
|
|
|
|
|
let static_dir = warp::get()
|
2023-05-13 13:37:24 +00:00
|
|
|
.and(with_renderer.clone())
|
2023-05-11 17:34:05 +00:00
|
|
|
.and(warp::path("static"))
|
|
|
|
.and(warp::path::full())
|
2023-05-13 13:37:24 +00:00
|
|
|
.map(|renderer: Renderer<'_, DM>, full: warp::path::FullPath| {
|
|
|
|
renderer.render(full.as_str(), &())
|
|
|
|
});
|
2023-05-12 12:51:10 +00:00
|
|
|
|
|
|
|
let index = warp::get()
|
2023-05-13 13:37:24 +00:00
|
|
|
.and(with_renderer.clone())
|
2023-05-12 12:51:10 +00:00
|
|
|
.and(warp::path::end())
|
2023-05-13 13:37:24 +00:00
|
|
|
.map(|renderer: Renderer<'_, DM>| renderer.render_page("/index.html", &()));
|
2023-05-12 12:51:10 +00:00
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct DomainGetNewRequest {
|
2023-05-12 14:43:28 +00:00
|
|
|
domain: domain::Name,
|
2023-05-12 12:51:10 +00:00
|
|
|
}
|
|
|
|
|
2023-05-12 16:17:23 +00:00
|
|
|
#[derive(Serialize)]
|
|
|
|
struct DomainGetNewResponse {
|
|
|
|
domain: domain::Name,
|
|
|
|
config: Option<domain::config::Config>,
|
|
|
|
}
|
|
|
|
|
2023-05-12 12:51:10 +00:00
|
|
|
let domain_get = warp::get()
|
2023-05-13 13:37:24 +00:00
|
|
|
.and(with_renderer.clone())
|
2023-05-12 16:17:23 +00:00
|
|
|
.and(warp::path!("domain.html"))
|
2023-05-12 12:51:10 +00:00
|
|
|
.and(warp::query::<DomainGetNewRequest>())
|
2023-05-12 16:17:23 +00:00
|
|
|
.and(warp::query::<util::ConfigFromURL>())
|
2023-05-12 14:43:28 +00:00
|
|
|
.map(
|
2023-05-13 13:37:24 +00:00
|
|
|
|renderer: Renderer<'_, DM>,
|
2023-05-12 16:17:23 +00:00
|
|
|
req: DomainGetNewRequest,
|
|
|
|
domain_config: util::ConfigFromURL| {
|
2023-05-13 13:37:24 +00:00
|
|
|
match renderer.domain_manager.get_config(&req.domain) {
|
|
|
|
Ok(_config) => renderer.render_error_page(500, "TODO not yet implemented"),
|
2023-05-13 13:22:47 +00:00
|
|
|
Err(domain::manager::GetConfigError::NotFound) => {
|
|
|
|
let domain_config = match domain_config.try_into() {
|
|
|
|
Ok(domain_config) => domain_config,
|
|
|
|
Err(e) => {
|
2023-05-13 13:37:24 +00:00
|
|
|
return renderer.render_error_page(
|
2023-05-13 13:22:47 +00:00
|
|
|
400,
|
2023-05-13 13:37:24 +00:00
|
|
|
format!("parsing domain configuration: {}", e).as_str(),
|
2023-05-13 13:22:47 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
renderer.render_page(
|
|
|
|
"/domain_get_new.html",
|
|
|
|
&DomainGetNewResponse {
|
2023-05-13 13:22:47 +00:00
|
|
|
domain: req.domain,
|
|
|
|
config: domain_config,
|
|
|
|
},
|
|
|
|
)
|
2023-05-12 16:17:23 +00:00
|
|
|
}
|
2023-05-13 13:37:24 +00:00
|
|
|
Err(domain::manager::GetConfigError::Unexpected(e)) => renderer
|
|
|
|
.render_error_page(
|
|
|
|
500,
|
|
|
|
format!("retrieving configuration: {}", e).as_str(),
|
|
|
|
),
|
2023-05-12 16:17:23 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct DomainPostRequest {
|
|
|
|
_init: bool,
|
|
|
|
domain: domain::Name,
|
|
|
|
passphrase: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
let domain_post = warp::post()
|
2023-05-13 13:37:24 +00:00
|
|
|
.and(with_renderer.clone())
|
2023-05-12 16:17:23 +00:00
|
|
|
.and(warp::path!("domain.html"))
|
|
|
|
.and(warp::query::<DomainPostRequest>())
|
|
|
|
.and(warp::query::<util::ConfigFromURL>())
|
|
|
|
.map(
|
2023-05-13 13:37:24 +00:00
|
|
|
|renderer: Renderer<'_, DM>,
|
2023-05-12 16:17:23 +00:00
|
|
|
req: DomainPostRequest,
|
|
|
|
domain_config: util::ConfigFromURL| {
|
2023-05-13 13:37:24 +00:00
|
|
|
if req.passphrase != renderer.passphrase.as_str() {
|
|
|
|
return renderer.render_error_page(401, "Incorrect passphrase");
|
2023-05-12 16:17:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//if req.init {
|
|
|
|
#[derive(Serialize)]
|
|
|
|
struct Response {
|
|
|
|
domain: domain::Name,
|
|
|
|
config: domain::config::Config,
|
|
|
|
target_cname: domain::Name,
|
|
|
|
challenge_token: String,
|
2023-05-12 14:43:28 +00:00
|
|
|
}
|
2023-05-12 16:17:23 +00:00
|
|
|
|
2023-05-13 13:22:47 +00:00
|
|
|
let config: domain::config::Config = match domain_config.try_into() {
|
|
|
|
Ok(Some(config)) => config,
|
|
|
|
Ok(None) => {
|
2023-05-13 13:37:24 +00:00
|
|
|
return renderer.render_error_page(400, "domain config is required")
|
2023-05-13 13:22:47 +00:00
|
|
|
}
|
|
|
|
Err(e) => {
|
2023-05-13 13:37:24 +00:00
|
|
|
return renderer
|
|
|
|
.render_error_page(400, format!("invalid domain config: {e}").as_str())
|
2023-05-13 13:22:47 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let config_hash = match config.hash() {
|
|
|
|
Ok(hash) => hash,
|
|
|
|
Err(e) => {
|
2023-05-13 13:37:24 +00:00
|
|
|
return renderer.render_error_page(
|
2023-05-13 13:22:47 +00:00
|
|
|
500,
|
2023-05-13 13:37:24 +00:00
|
|
|
format!("failed to hash domain config: {e}").as_str(),
|
2023-05-13 13:22:47 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
2023-05-12 16:17:23 +00:00
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
let target_cname = (*renderer.target_cname).clone();
|
2023-05-12 16:17:23 +00:00
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
return renderer.render_page(
|
|
|
|
"/domain_post_init.html",
|
|
|
|
&Response {
|
2023-05-12 16:17:23 +00:00
|
|
|
domain: req.domain,
|
|
|
|
config: config,
|
|
|
|
target_cname: target_cname,
|
|
|
|
challenge_token: config_hash,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
//}
|
2023-05-12 14:43:28 +00:00
|
|
|
},
|
|
|
|
);
|
2023-05-11 17:34:05 +00:00
|
|
|
|
2023-05-13 13:37:24 +00:00
|
|
|
let not_found = warp::any()
|
|
|
|
.and(with_renderer.clone())
|
|
|
|
.map(|renderer: Renderer<'_, DM>| renderer.render_error_page(404, "Page not found"));
|
2023-05-13 13:22:47 +00:00
|
|
|
|
|
|
|
Ok(static_dir
|
|
|
|
.or(index)
|
|
|
|
.or(domain_get)
|
|
|
|
.or(domain_post)
|
|
|
|
.or(not_found))
|
2023-05-11 12:19:36 +00:00
|
|
|
}
|