Allow HTTP to be used for configuring the server

This commit is contained in:
Maurus Decimus
2026-04-27 07:48:36 +02:00
parent cd4b08544c
commit 242bea67d9
8 changed files with 45 additions and 25 deletions

View File

@@ -14,7 +14,7 @@ pub mod token;
pub const DEVICE_CODE_LEN: usize = 40;
pub const USER_CODE_LEN: usize = 8;
pub const RANDOM_CODE_LEN: usize = 32;
pub const CLIENT_ID_MAX_LEN: usize = 20;
pub const CLIENT_ID_MAX_LEN: usize = 60;
pub const USER_CODE_ALPHABET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // No 0, O, I, 1

View File

@@ -81,7 +81,7 @@ impl ManagementApi for Server {
if let Some(email) = path.get(1).copied() {
self.is_http_anonymous_request_allowed(session.remote_ip)
.await?;
self.handle_discover_request(decode_path_element(email).as_ref())
self.handle_discover_request(session, decode_path_element(email).as_ref())
.await
} else {
Err(trc::ResourceEvent::NotFound.into_err())

View File

@@ -49,6 +49,7 @@ pub struct OAuthMetadata {
pub trait OAuthApiHandler: Sync + Send {
fn handle_discover_request(
&self,
session: &HttpSessionData,
account_name: &str,
) -> impl Future<Output = trc::Result<HttpResponse>> + Send;
@@ -114,7 +115,11 @@ pub enum LoginResponse {
}
impl OAuthApiHandler for Server {
async fn handle_discover_request(&self, account_name: &str) -> trc::Result<HttpResponse> {
async fn handle_discover_request(
&self,
session: &HttpSessionData,
account_name: &str,
) -> trc::Result<HttpResponse> {
let account_name = account_name.trim().to_lowercase();
if let Some(domain_name) = account_name.try_domain_part()
&& let Some(endpoint) = self
@@ -126,7 +131,7 @@ impl OAuthApiHandler for Server {
.no_cache()
.into_http_response())
} else {
self.handle_oidc_metadata().await
self.handle_oidc_metadata(!session.is_tls).await
}
}
@@ -155,13 +160,13 @@ impl OAuthApiHandler for Server {
if client_id.len() > CLIENT_ID_MAX_LEN {
return Err(trc::AuthEvent::Error
.into_err()
.details("Client ID is invalid."));
.details("Client ID is too long."));
} else if redirect_uri
.as_ref()
.is_some_and(|uri| uri.starts_with("http://"))
{
#[cfg(not(feature = "dev_mode"))]
if !self.registry().is_recovery_mode() {
if !self.registry().is_recovery_mode() && code_challenge.is_none() {
return Err(trc::AuthEvent::Error
.into_err()
.details("Redirect URI must be HTTPS."));

View File

@@ -33,7 +33,10 @@ pub trait OpenIdHandler: Sync + Send {
account_id: u32,
) -> impl Future<Output = trc::Result<HttpResponse>> + Send;
fn handle_oidc_metadata(&self) -> impl Future<Output = trc::Result<HttpResponse>> + Send;
fn handle_oidc_metadata(
&self,
strip_base_url: bool,
) -> impl Future<Output = trc::Result<HttpResponse>> + Send;
}
impl OpenIdHandler for Server {
@@ -52,8 +55,12 @@ impl OpenIdHandler for Server {
.into_http_response())
}
async fn handle_oidc_metadata(&self) -> trc::Result<HttpResponse> {
let base_url = &self.core.network.http.url_https;
async fn handle_oidc_metadata(&self, strip_base_url: bool) -> trc::Result<HttpResponse> {
let base_url = if strip_base_url {
""
} else {
&self.core.network.http.url_https
};
Ok(JsonResponse::new(OpenIdMetadata {
authorization_endpoint: format!("{base_url}/login",),

View File

@@ -278,7 +278,7 @@ impl ParseHttp for Server {
self.is_http_anonymous_request_allowed(session.remote_ip)
.await?;
return self.handle_oidc_metadata().await;
return self.handle_oidc_metadata(false).await;
}
("acme-challenge", &Method::GET) if self.has_acme_http_providers() => {
if let Some(token) = path.next() {

View File

@@ -38,6 +38,7 @@ use store::{
write::{AnyKey, BatchBuilder},
};
use types::id::Id;
use utils::is_valid_domain;
pub(crate) async fn bootstrap_get(
mut get: RegistryGetResponse<'_>,
@@ -505,15 +506,6 @@ pub(crate) async fn bootstrap_set(
Ok(set)
}
fn is_valid_domain(hostname: &str) -> bool {
const RESERVED_TLDS: &[&str] = &["test", "localhost", "local", "internal"];
psl::domain_str(hostname).is_some()
|| RESERVED_TLDS.contains(&hostname)
|| hostname
.rsplit_once('.')
.is_some_and(|(_, tld)| RESERVED_TLDS.contains(&tld))
}
async fn write_object(registry: &RegistryStore, object: &Object) -> Result<Id, SetError<Property>> {
match registry.write(RegistryWrite::insert(object)).await {
Ok(RegistryWriteResult::Success(id)) => Ok(id),

View File

@@ -340,12 +340,18 @@ pub fn sanitize_domain(domain: &str) -> Option<String> {
}
}
if found_dot
&& last_ch != '.'
&& psl::domain(result.as_bytes()).is_some_and(|d| d.suffix().typ().is_some())
{
if found_dot && last_ch != '.' && is_valid_domain(&result) {
Some(result)
} else {
None
}
}
pub fn is_valid_domain(domain: &str) -> bool {
const RESERVED_TLDS: &[&str] = &["test", "localhost", "local", "internal"];
psl::domain(domain.as_bytes()).is_some_and(|d| d.suffix().typ().is_some())
|| RESERVED_TLDS.contains(&domain)
|| domain
.rsplit_once('.')
.is_some_and(|(_, tld)| RESERVED_TLDS.contains(&tld))
}