Fixed client link
Build and Publish / Build and Publish Docker Image (push) Successful in 1m42s

This commit is contained in:
ab
2026-09-20 21:11:49 +03:00
parent 6449d8b59a
commit 46351351fc
4 changed files with 62 additions and 2 deletions
Generated
+1
View File
@@ -4772,6 +4772,7 @@ dependencies = [
"chrono-tz",
"cot",
"futures",
"idna",
"image",
"multer",
"password-auth",
+1
View File
@@ -20,6 +20,7 @@ tokio = { version = "1", features = ["fs", "process", "rt-multi-thread"] }
uuid = { version = "1", features = ["v4"] }
base64 = "0.22"
urlencoding = "2"
idna = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
web-push-native = "0.5"
+58 -1
View File
@@ -322,6 +322,39 @@ struct ClientFormTemplate<'a> {
client_id: i64,
client_status: &'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)]
@@ -1270,6 +1303,7 @@ async fn client_new_page(request: Request, session: Session) -> cot::Result<Resp
client_id: 0,
client_status: "",
client_token: "",
portal_base: String::new(),
}
.render()?;
html_response(body, lang)
@@ -1295,6 +1329,13 @@ async fn client_edit_page(
let t = lang.t();
let action_url = format!("/admin/clients/{}/save", client.id);
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 {
t,
lang,
@@ -1312,6 +1353,7 @@ async fn client_edit_page(
client_id: cid,
client_status: &client.status,
client_token: &client.media_token,
portal_base,
}
.render()?;
html_response(body, lang)
@@ -3544,7 +3586,7 @@ pub fn admin_router() -> Router {
#[cfg(test)]
mod tests {
use super::preserved_secret_value;
use super::{humanize_domain, preserved_secret_value};
#[test]
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", "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(""), "");
}
}
+2 -1
View File
@@ -72,7 +72,8 @@
<script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"></script>
<script>
(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;
new QRious({ element: document.getElementById('qrCanvas'), value: url, size: 180, level: 'M' });
})();