Add secret service.http.form_method field for debugging
This commit is contained in:
parent
4483185e75
commit
5a4ff4ca65
@ -3,6 +3,8 @@ origin:
|
||||
domain:
|
||||
store_dir_path: /tmp/domani_dev_env/domain
|
||||
service:
|
||||
http:
|
||||
form_method: GET
|
||||
passphrase: foobar
|
||||
dns_records:
|
||||
- kind: A
|
||||
|
@ -41,5 +41,6 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub dns: ConfigDNS,
|
||||
pub acme: Option<ConfigACME>,
|
||||
#[serde(default)]
|
||||
pub builtins: collections::HashMap<domain::Name, BuiltinDomain>,
|
||||
}
|
||||
|
@ -1,38 +1,5 @@
|
||||
mod config;
|
||||
pub mod http;
|
||||
mod util;
|
||||
|
||||
use crate::domain;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{net, str::FromStr};
|
||||
|
||||
fn default_primary_domain() -> domain::Name {
|
||||
domain::Name::from_str("localhost").unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ConfigDNSRecord {
|
||||
A { addr: net::Ipv4Addr },
|
||||
AAAA { addr: net::Ipv6Addr },
|
||||
CNAME { name: domain::Name },
|
||||
}
|
||||
|
||||
impl From<ConfigDNSRecord> for domain::checker::DNSRecord {
|
||||
fn from(r: ConfigDNSRecord) -> Self {
|
||||
match r {
|
||||
ConfigDNSRecord::A { addr } => Self::A(addr),
|
||||
ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr),
|
||||
ConfigDNSRecord::CNAME { name } => Self::CNAME(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub passphrase: String,
|
||||
pub dns_records: Vec<ConfigDNSRecord>,
|
||||
#[serde(default = "default_primary_domain")]
|
||||
pub primary_domain: domain::Name,
|
||||
#[serde(default)]
|
||||
pub http: self::http::Config,
|
||||
}
|
||||
pub use config::*;
|
||||
|
35
src/service/config.rs
Normal file
35
src/service/config.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use crate::{domain, service};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{net, str::FromStr};
|
||||
|
||||
fn default_primary_domain() -> domain::Name {
|
||||
domain::Name::from_str("localhost").unwrap()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ConfigDNSRecord {
|
||||
A { addr: net::Ipv4Addr },
|
||||
AAAA { addr: net::Ipv6Addr },
|
||||
CNAME { name: domain::Name },
|
||||
}
|
||||
|
||||
impl From<ConfigDNSRecord> for domain::checker::DNSRecord {
|
||||
fn from(r: ConfigDNSRecord) -> Self {
|
||||
match r {
|
||||
ConfigDNSRecord::A { addr } => Self::A(addr),
|
||||
ConfigDNSRecord::AAAA { addr } => Self::AAAA(addr),
|
||||
ConfigDNSRecord::CNAME { name } => Self::CNAME(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub passphrase: String,
|
||||
pub dns_records: Vec<ConfigDNSRecord>,
|
||||
#[serde(default = "default_primary_domain")]
|
||||
pub primary_domain: domain::Name,
|
||||
#[serde(default)]
|
||||
pub http: service::http::Config,
|
||||
}
|
@ -4,6 +4,7 @@ mod tpl;
|
||||
|
||||
pub use config::*;
|
||||
|
||||
use http::request::Parts;
|
||||
use hyper::{Body, Method, Request, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -48,6 +49,7 @@ pub fn new(
|
||||
#[derive(Serialize)]
|
||||
struct BasePresenter<'a, T> {
|
||||
page_name: &'a str,
|
||||
form_method: &'a str,
|
||||
data: T,
|
||||
}
|
||||
|
||||
@ -59,12 +61,18 @@ struct DomainGetArgs {
|
||||
#[derive(Deserialize)]
|
||||
struct DomainInitArgs {
|
||||
domain: domain::Name,
|
||||
|
||||
#[serde(flatten)]
|
||||
domain_config: service::util::FlatConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DomainSyncArgs {
|
||||
domain: domain::Name,
|
||||
passphrase: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
domain_config: service::util::FlatConfig,
|
||||
}
|
||||
|
||||
impl<'svc> Service {
|
||||
@ -121,6 +129,7 @@ impl<'svc> Service {
|
||||
"/base.html",
|
||||
BasePresenter {
|
||||
page_name: "/error.html",
|
||||
form_method: self.config.http.form_method.as_str(),
|
||||
data: &Response { error_msg: e },
|
||||
},
|
||||
)
|
||||
@ -143,6 +152,7 @@ impl<'svc> Service {
|
||||
"/base.html",
|
||||
BasePresenter {
|
||||
page_name: name,
|
||||
form_method: self.config.http.form_method.as_str(),
|
||||
data,
|
||||
},
|
||||
)
|
||||
@ -174,14 +184,33 @@ impl<'svc> Service {
|
||||
}
|
||||
}
|
||||
|
||||
async fn with_query_req<'a, F, In, Out>(&self, req: &'a Request<Body>, f: F) -> Response<Body>
|
||||
async fn with_query_req<'a, F, In, Out>(
|
||||
&self,
|
||||
req: &'a Parts,
|
||||
body: Body,
|
||||
f: F,
|
||||
) -> Response<Body>
|
||||
where
|
||||
In: Deserialize<'a>,
|
||||
In: for<'d> Deserialize<'d>,
|
||||
F: FnOnce(In) -> Out,
|
||||
Out: future::Future<Output = Response<Body>>,
|
||||
{
|
||||
let query = req.uri().query().unwrap_or("");
|
||||
match serde_urlencoded::from_str::<In>(query) {
|
||||
let res = match self.config.http.form_method {
|
||||
ConfigFormMethod::GET => {
|
||||
serde_urlencoded::from_str::<In>(req.uri.query().unwrap_or(""))
|
||||
}
|
||||
ConfigFormMethod::POST => {
|
||||
let body = match hyper::body::to_bytes(body).await {
|
||||
Ok(bytes) => bytes.to_vec(),
|
||||
Err(e) => {
|
||||
return self.internal_error(format!("failed to read body: {e}").as_str())
|
||||
}
|
||||
};
|
||||
serde_urlencoded::from_bytes::<In>(body.as_ref())
|
||||
}
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(args) => f(args).await,
|
||||
Err(err) => {
|
||||
self.render_error_page(400, format!("failed to parse query args: {}", err).as_str())
|
||||
@ -219,11 +248,7 @@ impl<'svc> Service {
|
||||
)
|
||||
}
|
||||
|
||||
fn domain_init(
|
||||
&self,
|
||||
args: DomainInitArgs,
|
||||
domain_config: service::util::FlatConfig,
|
||||
) -> Response<Body> {
|
||||
fn domain_init(&self, args: DomainInitArgs) -> Response<Body> {
|
||||
#[derive(Serialize)]
|
||||
struct Response<'a> {
|
||||
domain: domain::Name,
|
||||
@ -235,7 +260,7 @@ impl<'svc> Service {
|
||||
dns_records_have_cname: bool,
|
||||
}
|
||||
|
||||
let config: domain::Domain = match domain_config.try_into() {
|
||||
let config: domain::Domain = match args.domain_config.try_into() {
|
||||
Ok(Some(config)) => config,
|
||||
Ok(None) => return self.render_error_page(400, "domain config is required"),
|
||||
Err(e) => {
|
||||
@ -272,16 +297,12 @@ impl<'svc> Service {
|
||||
)
|
||||
}
|
||||
|
||||
async fn domain_sync(
|
||||
&self,
|
||||
args: DomainSyncArgs,
|
||||
domain_config: service::util::FlatConfig,
|
||||
) -> Response<Body> {
|
||||
async fn domain_sync(&self, args: DomainSyncArgs) -> Response<Body> {
|
||||
if args.passphrase != self.config.passphrase.as_str() {
|
||||
return self.render_error_page(401, "Incorrect passphrase");
|
||||
}
|
||||
|
||||
let config: domain::Domain = match domain_config.try_into() {
|
||||
let config: domain::Domain = match args.domain_config.try_into() {
|
||||
Ok(Some(config)) => config,
|
||||
Ok(None) => return self.render_error_page(400, "domain config is required"),
|
||||
Err(e) => {
|
||||
@ -341,12 +362,14 @@ impl<'svc> Service {
|
||||
}
|
||||
|
||||
async fn handle_request(&self, req: Request<Body>) -> Response<Body> {
|
||||
let (req, body) = req.into_parts();
|
||||
|
||||
let maybe_host = match (
|
||||
req.headers()
|
||||
req.headers
|
||||
.get("Host")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(strip_port),
|
||||
req.uri().host().map(strip_port),
|
||||
req.uri.host().map(strip_port),
|
||||
) {
|
||||
(Some(h), _) if h != self.config.primary_domain.as_str() => Some(h),
|
||||
(_, Some(h)) if h != self.config.primary_domain.as_str() => Some(h),
|
||||
@ -354,13 +377,12 @@ impl<'svc> Service {
|
||||
}
|
||||
.and_then(|h| domain::Name::from_str(h).ok());
|
||||
|
||||
let method = req.method();
|
||||
let path = req.uri().path();
|
||||
let path = req.uri.path();
|
||||
|
||||
// Serving acme challenges always takes priority. We serve them from the same store no
|
||||
// matter the domain, presumably they are cryptographically random enough that it doesn't
|
||||
// matter.
|
||||
if method == Method::GET && path.starts_with("/.well-known/acme-challenge/") {
|
||||
if req.method == Method::GET && path.starts_with("/.well-known/acme-challenge/") {
|
||||
let token = path.trim_start_matches("/.well-known/acme-challenge/");
|
||||
|
||||
if let Ok(key) = self.domain_manager.get_acme_http01_challenge_key(token) {
|
||||
@ -369,7 +391,7 @@ impl<'svc> Service {
|
||||
}
|
||||
|
||||
// Serving domani challenges similarly takes priority.
|
||||
if method == Method::GET && path == "/.well-known/domani-challenge" {
|
||||
if req.method == Method::GET && path == "/.well-known/domani-challenge" {
|
||||
if let Some(ref domain) = maybe_host {
|
||||
match self
|
||||
.domain_manager
|
||||
@ -388,38 +410,36 @@ impl<'svc> Service {
|
||||
|
||||
// If a managed domain was given then serve that from its origin
|
||||
if let Some(domain) = maybe_host {
|
||||
return self.serve_origin(domain, req.uri().path());
|
||||
return self.serve_origin(domain, req.uri.path());
|
||||
}
|
||||
|
||||
// Serve main domani site
|
||||
|
||||
if method == Method::GET && path.starts_with("/static/") {
|
||||
if req.method == Method::GET && path.starts_with("/static/") {
|
||||
return self.render(200, path, ());
|
||||
}
|
||||
|
||||
match (method, path) {
|
||||
let config_form_method = self.config.http.form_method.as_ref();
|
||||
|
||||
match (&req.method, path) {
|
||||
(&Method::GET, "/") | (&Method::GET, "/index.html") => {
|
||||
self.render_page("/index.html", ())
|
||||
}
|
||||
(&Method::GET, "/domain.html") => {
|
||||
self.with_query_req(&req, |args: DomainGetArgs| async { self.domain_get(args) })
|
||||
.await
|
||||
}
|
||||
(&Method::GET, "/domain_init.html") => {
|
||||
self.with_query_req(&req, |args: DomainInitArgs| async {
|
||||
self.with_query_req(&req, |config: service::util::FlatConfig| async {
|
||||
self.domain_init(args, config)
|
||||
})
|
||||
.await
|
||||
(form_method, "/domain.html") if form_method == config_form_method => {
|
||||
self.with_query_req(&req, body, |args: DomainGetArgs| async {
|
||||
self.domain_get(args)
|
||||
})
|
||||
.await
|
||||
}
|
||||
(&Method::GET, "/domain_sync.html") => {
|
||||
self.with_query_req(&req, |args: DomainSyncArgs| async {
|
||||
self.with_query_req(&req, |config: service::util::FlatConfig| async {
|
||||
self.domain_sync(args, config).await
|
||||
(form_method, "/domain_init.html") if form_method == config_form_method => {
|
||||
self.with_query_req(&req, body, |args: DomainInitArgs| async {
|
||||
self.domain_init(args)
|
||||
})
|
||||
.await
|
||||
}
|
||||
(form_method, "/domain_sync.html") if form_method == config_form_method => {
|
||||
self.with_query_req(&req, body, |args: DomainSyncArgs| async {
|
||||
self.domain_sync(args).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -5,11 +5,44 @@ fn default_http_addr() -> net::SocketAddr {
|
||||
net::SocketAddr::from_str("[::]:3030").unwrap()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub enum ConfigFormMethod {
|
||||
GET,
|
||||
POST,
|
||||
}
|
||||
|
||||
impl ConfigFormMethod {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::GET => "GET",
|
||||
Self::POST => "POST",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigFormMethod {
|
||||
fn default() -> Self {
|
||||
Self::POST
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<hyper::Method> for ConfigFormMethod {
|
||||
fn as_ref(&self) -> &hyper::Method {
|
||||
match self {
|
||||
Self::GET => &hyper::Method::GET,
|
||||
Self::POST => &hyper::Method::POST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_http_addr")]
|
||||
pub http_addr: net::SocketAddr,
|
||||
pub https_addr: Option<net::SocketAddr>,
|
||||
|
||||
#[serde(default)]
|
||||
pub form_method: ConfigFormMethod,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -17,6 +50,7 @@ impl Default for Config {
|
||||
Self {
|
||||
http_addr: default_http_addr(),
|
||||
https_addr: None,
|
||||
form_method: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ automatically updated too!</p>
|
||||
<p><em>In the future Domani will support more backends than just git
|
||||
repos.</em></p>
|
||||
|
||||
<form method="GET" action="/domain_init.html">
|
||||
<form method="{{ form_method }}" action="/domain_init.html">
|
||||
<input name="domain" type="hidden" value="{{ data.domain }}" />
|
||||
<input name="config_origin_descr_kind" type="hidden" value="git" />
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<p>This step requires a passphrase that has been given to you by the
|
||||
administrator of the Domani server:</p>
|
||||
|
||||
<form method="GET" action="/domain_sync.html" id="syncForm">
|
||||
<form method="{{ form_method }}" action="/domain_sync.html" id="syncForm">
|
||||
<input name="domain" type="hidden" value="{{ data.domain }}" />
|
||||
{{ #each data.flat_config }}
|
||||
<input name="{{ @key }}" type="hidden" value="{{ this }}" />
|
||||
@ -47,7 +47,7 @@ query for your domain name. It can be <strong>one or more of</strong>:</p>
|
||||
|
||||
{{ #each data.dns_records }}
|
||||
<tr>
|
||||
<td>{{ this.type }}</td>
|
||||
<td>{{ this.kind }}</td>
|
||||
<td>{{ lookup ../data "domain" }}</td>
|
||||
{{ #if this.name }}
|
||||
<td>{{ this.name }}</td>
|
||||
|
@ -13,7 +13,7 @@ server, and you're done!</p>
|
||||
<p>Input your domain name below to set it up, or to reconfigure it has already
|
||||
been set up.</p>
|
||||
|
||||
<form method="get" action="/domain.html">
|
||||
<form method="{{ form_method }}" action="/domain.html">
|
||||
|
||||
<fieldset>
|
||||
<label>
|
||||
|
Loading…
Reference in New Issue
Block a user