agent_first_http/cli/cmd/
panel.rs1use serde::Serialize;
4
5use crate::cli::connect::Connection;
6use crate::cli::output;
7use crate::shared::error::Error;
8
9#[derive(Debug)]
10pub struct Args {
11 pub connection: Connection,
12}
13
14#[derive(Serialize)]
15struct PanelResult {
16 takeover_url_secret: String,
17 takeover_url_expires_at_rfc3339: String,
18 takeover_url_ttl_s: u64,
19 takeover_url_scope: String,
20 provider: Option<String>,
21 #[serde(skip_serializing_if = "Vec::is_empty")]
22 warnings: Vec<String>,
23}
24
25pub async fn run(args: Args) -> Result<(), Error> {
26 let result = build_result(&args.connection).await?;
27 output::emit_revealing_takeover("panel", &result)
28}
29
30async fn build_result(connection: &Connection) -> Result<PanelResult, Error> {
31 let client = connection.client().await?;
32 let handoff = client.takeover_handoff(None, None).await?;
33 let mut warnings = Vec::new();
34 let mut provider = None;
35 match client.capabilities().await {
36 Ok(caps) => {
37 if caps.takeover.supported {
38 provider = caps.takeover.provider;
39 } else {
40 warnings.push(
41 "this host has no takeover panel; build a takeover-ready host with `afhttp container install` and reconnect. This is not a captcha bypass.".into(),
42 );
43 }
44 }
45 Err(e) => {
46 warnings.push(format!(
47 "could not read host capabilities; returning the takeover_url_secret anyway: {}",
48 e.detail
49 ));
50 }
51 }
52 Ok(PanelResult {
53 takeover_url_secret: handoff.takeover_url_secret,
54 takeover_url_expires_at_rfc3339: handoff.takeover_url_expires_at_rfc3339,
55 takeover_url_ttl_s: handoff.takeover_url_ttl_s,
56 takeover_url_scope: handoff.takeover_url_scope,
57 provider,
58 warnings,
59 })
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn panel_result_exposes_takeover_url_secret_only() {
68 let value = serde_json::to_value(PanelResult {
69 takeover_url_secret: "http://localhost:9222/takeover/panel?handoff_secret=h".into(),
70 takeover_url_expires_at_rfc3339: "2026-06-11T00:00:00Z".into(),
71 takeover_url_ttl_s: 900,
72 takeover_url_scope: "takeover".into(),
73 provider: Some("kasmvnc".into()),
74 warnings: Vec::new(),
75 })
76 .unwrap();
77 assert!(value.get("url").is_none());
78 assert!(value.get("display_url").is_none());
79 assert!(value.get("panel_url").is_none());
80 assert!(value.get("token_secret").is_none());
81 assert!(
82 value["takeover_url_secret"]
83 .as_str()
84 .unwrap()
85 .contains("handoff_secret=")
86 );
87 assert!(value.get("provider").is_some());
88 }
89}