This commit is contained in:
Generated
+1
@@ -4772,6 +4772,7 @@ dependencies = [
|
|||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"cot",
|
"cot",
|
||||||
"futures",
|
"futures",
|
||||||
|
"idna",
|
||||||
"image",
|
"image",
|
||||||
"multer",
|
"multer",
|
||||||
"password-auth",
|
"password-auth",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ tokio = { version = "1", features = ["fs", "process", "rt-multi-thread"] }
|
|||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
urlencoding = "2"
|
urlencoding = "2"
|
||||||
|
idna = "1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
web-push-native = "0.5"
|
web-push-native = "0.5"
|
||||||
|
|||||||
+58
-1
@@ -322,6 +322,39 @@ struct ClientFormTemplate<'a> {
|
|||||||
client_id: i64,
|
client_id: i64,
|
||||||
client_status: &'a str,
|
client_status: &'a str,
|
||||||
client_token: &'a str,
|
client_token: &'a str,
|
||||||
|
/// Base origin for the portal link with any punycode host decoded to Unicode
|
||||||
|
/// (e.g. `https://мурняня.рф`); empty falls back to the browser origin.
|
||||||
|
portal_base: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes any punycode (`xn--…`) labels in a URL's host to human-readable
|
||||||
|
/// Unicode, keeping the scheme and trimming a trailing slash. Returns an empty
|
||||||
|
/// string for empty input.
|
||||||
|
fn humanize_domain(raw: &str) -> String {
|
||||||
|
let raw = raw.trim();
|
||||||
|
if raw.is_empty() {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
let (scheme, rest) = match raw.split_once("://") {
|
||||||
|
Some((s, r)) => (Some(s), r),
|
||||||
|
None => (None, raw),
|
||||||
|
};
|
||||||
|
let (host, path) = match rest.split_once('/') {
|
||||||
|
Some((h, p)) => (h, Some(p)),
|
||||||
|
None => (rest, None),
|
||||||
|
};
|
||||||
|
let (unicode_host, _) = idna::domain_to_unicode(host);
|
||||||
|
let mut out = String::new();
|
||||||
|
if let Some(s) = scheme {
|
||||||
|
out.push_str(s);
|
||||||
|
out.push_str("://");
|
||||||
|
}
|
||||||
|
out.push_str(&unicode_host);
|
||||||
|
if let Some(p) = path.filter(|p| !p.is_empty()) {
|
||||||
|
out.push('/');
|
||||||
|
out.push_str(p);
|
||||||
|
}
|
||||||
|
out.trim_end_matches('/').to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Template)]
|
#[derive(Debug, Template)]
|
||||||
@@ -1270,6 +1303,7 @@ async fn client_new_page(request: Request, session: Session) -> cot::Result<Resp
|
|||||||
client_id: 0,
|
client_id: 0,
|
||||||
client_status: "",
|
client_status: "",
|
||||||
client_token: "",
|
client_token: "",
|
||||||
|
portal_base: String::new(),
|
||||||
}
|
}
|
||||||
.render()?;
|
.render()?;
|
||||||
html_response(body, lang)
|
html_response(body, lang)
|
||||||
@@ -1295,6 +1329,13 @@ async fn client_edit_page(
|
|||||||
let t = lang.t();
|
let t = lang.t();
|
||||||
let action_url = format!("/admin/clients/{}/save", client.id);
|
let action_url = format!("/admin/clients/{}/save", client.id);
|
||||||
let cid = client.id.unwrap();
|
let cid = client.id.unwrap();
|
||||||
|
let site_domain_key = "site_domain".to_string();
|
||||||
|
let site_domain = query!(Setting, $key == site_domain_key)
|
||||||
|
.get(&db)
|
||||||
|
.await?
|
||||||
|
.map(|s| s.value)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let portal_base = humanize_domain(&site_domain);
|
||||||
let body = ClientFormTemplate {
|
let body = ClientFormTemplate {
|
||||||
t,
|
t,
|
||||||
lang,
|
lang,
|
||||||
@@ -1312,6 +1353,7 @@ async fn client_edit_page(
|
|||||||
client_id: cid,
|
client_id: cid,
|
||||||
client_status: &client.status,
|
client_status: &client.status,
|
||||||
client_token: &client.media_token,
|
client_token: &client.media_token,
|
||||||
|
portal_base,
|
||||||
}
|
}
|
||||||
.render()?;
|
.render()?;
|
||||||
html_response(body, lang)
|
html_response(body, lang)
|
||||||
@@ -3544,7 +3586,7 @@ pub fn admin_router() -> Router {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::preserved_secret_value;
|
use super::{humanize_domain, preserved_secret_value};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keeps_masks_and_updates_secrets_consistently() {
|
fn keeps_masks_and_updates_secrets_consistently() {
|
||||||
@@ -3553,4 +3595,19 @@ mod tests {
|
|||||||
assert_eq!(preserved_secret_value("stored", "", true), "");
|
assert_eq!(preserved_secret_value("stored", "", true), "");
|
||||||
assert_eq!(preserved_secret_value("stored", "new", true), "new");
|
assert_eq!(preserved_secret_value("stored", "new", true), "new");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decodes_punycode_host_to_unicode() {
|
||||||
|
assert_eq!(
|
||||||
|
humanize_domain("https://xn--l1acako8eb.xn--p1ai"),
|
||||||
|
"https://мурняня.рф"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
humanize_domain("https://xn--l1acako8eb.xn--p1ai/"),
|
||||||
|
"https://мурняня.рф"
|
||||||
|
);
|
||||||
|
// Plain ASCII domains are returned unchanged (minus trailing slash).
|
||||||
|
assert_eq!(humanize_domain("https://example.com/"), "https://example.com");
|
||||||
|
assert_eq!(humanize_domain(""), "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,8 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var url = window.location.origin + '/client/{{ client_token }}';
|
var base = '{{ portal_base }}' || window.location.origin;
|
||||||
|
var url = base + '/client/{{ client_token }}';
|
||||||
document.getElementById('portalUrl').value = url;
|
document.getElementById('portalUrl').value = url;
|
||||||
new QRious({ element: document.getElementById('qrCanvas'), value: url, size: 180, level: 'M' });
|
new QRious({ element: document.getElementById('qrCanvas'), value: url, size: 180, level: 'M' });
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user