e9c5030190
apple / swift (push) Successful in 1m7s
audit / cargo-audit (push) Successful in 1m14s
ci / rust (push) Failing after 49s
ci / web (push) Successful in 52s
windows-host / package (push) Failing after 2m58s
ci / docs-site (push) Successful in 1m5s
android / android (push) Successful in 4m7s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m15s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m15s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 48s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 49s
ci / bench (push) Successful in 5m5s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
release / apple (push) Successful in 8m30s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
apple / screenshots (push) Has been cancelled
docker / deploy-docs (push) Successful in 19s
Each client learns a host's MAC from the mDNS `mac` TXT while it's awake, persists it on the saved-host record, and — when reconnecting to an offline host — sends a magic packet before connecting, plus an explicit "Wake host" action. Apple wraps the C-ABI; linux/windows call the core fn directly (linux also gains a --wake CLI mode); android via a new nativeWakeOnLan JNI export (the mDNS browse record gains a 7th mac field); decky shells out to the linux client's --wake before launching the stream. iOS/tvOS need the managed com.apple.developer.networking.multicast entitlement (pending Apple approval), so the wake path + UI are gated off via PunktfunkConnection.wakeOnLANAvailable and the entitlement is commented out — keeping iOS/tvOS releasable. MAC-learning stays active on every platform so it lights up the moment it's ungated. macOS works today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
5.0 KiB
Rust
128 lines
5.0 KiB
Rust
//! The SPAKE2 PIN pairing screen: the host is armed and displays a 4-digit PIN; proving
|
|
//! knowledge of it pins the host's certificate (and registers ours) with no offline-guessable
|
|
//! transcript. Also offers the no-PIN "request access" (delegated-approval) alternative.
|
|
|
|
use super::connect::{connect, request_access};
|
|
use super::style::*;
|
|
use super::{Screen, Svc};
|
|
use crate::trust::{self, KnownHost, KnownHosts};
|
|
use punktfunk_core::client::NativeClient;
|
|
use windows_reactor::*;
|
|
|
|
pub(crate) fn pair_page(props: &Svc, cx: &mut RenderCx) -> Element {
|
|
let ctx = &props.ctx;
|
|
let set_screen = &props.set_screen;
|
|
let set_status = &props.set_status;
|
|
let (code, set_code) = cx.use_state(String::new());
|
|
let target = ctx.shared.target.lock().unwrap().clone();
|
|
|
|
let pair_btn = {
|
|
let (ctx2, ss, st, code2, target2) = (
|
|
ctx.clone(),
|
|
set_screen.clone(),
|
|
set_status.clone(),
|
|
code.clone(),
|
|
target.clone(),
|
|
);
|
|
button("Pair & Connect")
|
|
.accent()
|
|
.icon(Symbol::Accept)
|
|
.on_click(move || {
|
|
let pin = code2.trim().to_string();
|
|
let (ctx3, ss, st, target3) =
|
|
(ctx2.clone(), ss.clone(), st.clone(), target2.clone());
|
|
std::thread::spawn(move || {
|
|
let name =
|
|
std::env::var("COMPUTERNAME").unwrap_or_else(|_| "windows-client".into());
|
|
match NativeClient::pair(
|
|
&target3.addr,
|
|
target3.port,
|
|
(&ctx3.identity.0, &ctx3.identity.1),
|
|
&pin,
|
|
&name,
|
|
std::time::Duration::from_secs(90),
|
|
) {
|
|
Ok(fp) => {
|
|
let mut k = KnownHosts::load();
|
|
k.upsert(KnownHost {
|
|
name: target3.name.clone(),
|
|
addr: target3.addr.clone(),
|
|
port: target3.port,
|
|
fp_hex: trust::hex(&fp),
|
|
paired: true,
|
|
mac: target3.mac.clone(),
|
|
});
|
|
let _ = k.save();
|
|
connect(&ctx3, &target3, Some(fp), &ss, &st);
|
|
}
|
|
Err(e) => {
|
|
st.call(format!("Pairing failed: {e:?} (wrong PIN, or not armed?)"));
|
|
ss.call(Screen::Hosts);
|
|
}
|
|
}
|
|
});
|
|
})
|
|
};
|
|
let cancel_btn = {
|
|
let ss = set_screen.clone();
|
|
button("Cancel")
|
|
.icon(Symbol::Cancel)
|
|
.on_click(move || ss.call(Screen::Hosts))
|
|
};
|
|
// The no-PIN alternative offered alongside the PIN ceremony: open an identified connect that
|
|
// the host parks until the operator approves this device in its console (delegated approval).
|
|
let request_btn = {
|
|
let (svc, target2) = (props.clone(), target.clone());
|
|
button("Request access without a PIN")
|
|
.icon(Symbol::Send)
|
|
.on_click(move || request_access(&svc, &target2))
|
|
.horizontal_alignment(HorizontalAlignment::Stretch)
|
|
};
|
|
|
|
let content = card(vstack((
|
|
grid((
|
|
avatar(&target.name)
|
|
.grid_column(0)
|
|
.vertical_alignment(VerticalAlignment::Center),
|
|
vstack((
|
|
text_block(format!("Pair with {}", target.name))
|
|
.font_size(20.0)
|
|
.semibold(),
|
|
text_block(format!("{}:{}", target.addr, target.port))
|
|
.font_size(12.0)
|
|
.foreground(ThemeRef::SecondaryText),
|
|
))
|
|
.spacing(2.0)
|
|
.grid_column(1)
|
|
.vertical_alignment(VerticalAlignment::Center)
|
|
.margin(edges(12.0, 0.0, 0.0, 0.0)),
|
|
))
|
|
.columns([GridLength::Auto, GridLength::Star(1.0)]),
|
|
InfoBar::new("Arm pairing on the host")
|
|
.message(
|
|
"On the host's console or web console, start pairing — it shows a 4-digit PIN. \
|
|
Enter it below within 90 seconds.",
|
|
)
|
|
.informational()
|
|
.is_closable(false),
|
|
text_box(code)
|
|
.placeholder_text("PIN")
|
|
.font_size(28.0)
|
|
.on_text_changed(move |s| set_code.call(s)),
|
|
hstack((pair_btn, cancel_btn)).spacing(8.0),
|
|
text_block(
|
|
"Don\u{2019}t have a PIN? Request access instead and approve this device on the host \
|
|
(its console or web UI) \u{2014} no PIN needed.",
|
|
)
|
|
.font_size(12.0)
|
|
.foreground(ThemeRef::SecondaryText),
|
|
request_btn,
|
|
))
|
|
.spacing(16.0))
|
|
.max_width(480.0)
|
|
.horizontal_alignment(HorizontalAlignment::Center)
|
|
.margin(edges(0.0, 60.0, 0.0, 0.0));
|
|
|
|
page(vec![content.into()])
|
|
}
|