diff --git a/crates/sshx-core/proto/sshx.proto b/crates/sshx-core/proto/sshx.proto index 111d0dda..cdc8b558 100644 --- a/crates/sshx-core/proto/sshx.proto +++ b/crates/sshx-core/proto/sshx.proto @@ -41,7 +41,8 @@ message OpenRequest { string origin = 1; // Web origin of the server. bytes encrypted_zeros = 2; // Encrypted zero block, for client verification. string name = 3; // Name of the session (user@hostname). - optional bytes write_password_hash = 4; // Hashed write password, if read-only mode is enabled. + string addr = 4; // Sub path of the URL + optional bytes write_password_hash = 5; // Hashed write password, if write protection is not disabled. } // Details of a newly-created sshx session. diff --git a/crates/sshx-server/src/grpc.rs b/crates/sshx-server/src/grpc.rs index d68cb61b..99eba829 100644 --- a/crates/sshx-server/src/grpc.rs +++ b/crates/sshx-server/src/grpc.rs @@ -48,7 +48,11 @@ impl SshxService for GrpcServer { if origin.is_empty() { return Err(Status::invalid_argument("origin is empty")); } - let name = rand_alphanumeric(10); + let name = if request.addr.is_empty() { + rand_alphanumeric(10) + } else { + request.addr + }; info!(%name, "creating new session"); match self.0.lookup(&name) { @@ -195,19 +199,19 @@ async fn handle_update(tx: &ServerTx, session: &Session, update: ClientUpdate) - } Some(ClientMessage::Data(data)) => { if let Err(err) = session.add_data(Sid(data.id), data.data, data.seq) { - return send_err(tx, format!("add data: {:?}", err)).await; + return send_err(tx, format!("add data: {err:?}")).await; } } Some(ClientMessage::CreatedShell(new_shell)) => { let id = Sid(new_shell.id); let center = (new_shell.x, new_shell.y); if let Err(err) = session.add_shell(id, center) { - return send_err(tx, format!("add shell: {:?}", err)).await; + return send_err(tx, format!("add shell: {err:?}")).await; } } Some(ClientMessage::ClosedShell(id)) => { if let Err(err) = session.close_shell(Sid(id)) { - return send_err(tx, format!("close shell: {:?}", err)).await; + return send_err(tx, format!("close shell: {err:?}")).await; } } Some(ClientMessage::Pong(ts)) => { diff --git a/crates/sshx-server/tests/common/mod.rs b/crates/sshx-server/tests/common/mod.rs index f86b06c7..f28de4ed 100644 --- a/crates/sshx-server/tests/common/mod.rs +++ b/crates/sshx-server/tests/common/mod.rs @@ -153,7 +153,7 @@ impl ClientSocket { let msg = self.inner.next().await.unwrap().unwrap(); match msg { Message::Close(Some(frame)) => assert!(frame.code == code.into()), - _ => panic!("unexpected non-close message over WebSocket: {:?}", msg), + _ => panic!("unexpected non-close message over WebSocket: {msg:?}"), } } diff --git a/crates/sshx-server/tests/simple.rs b/crates/sshx-server/tests/simple.rs index 3549030d..3f25c422 100644 --- a/crates/sshx-server/tests/simple.rs +++ b/crates/sshx-server/tests/simple.rs @@ -14,6 +14,7 @@ async fn test_rpc() -> Result<()> { let req = OpenRequest { origin: "sshx.io".into(), encrypted_zeros: Encrypt::new("").zeros().into(), + addr: "".into(), name: String::new(), write_password_hash: None, }; diff --git a/crates/sshx-server/tests/snapshot.rs b/crates/sshx-server/tests/snapshot.rs index dde5f6e8..3b8ffac4 100644 --- a/crates/sshx-server/tests/snapshot.rs +++ b/crates/sshx-server/tests/snapshot.rs @@ -16,7 +16,7 @@ pub mod common; async fn test_basic_restore() -> Result<()> { let server = TestServer::new().await; - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); tokio::spawn(async move { controller.run().await }); diff --git a/crates/sshx-server/tests/with_client.rs b/crates/sshx-server/tests/with_client.rs index d52222f2..5c285c2d 100644 --- a/crates/sshx-server/tests/with_client.rs +++ b/crates/sshx-server/tests/with_client.rs @@ -14,7 +14,7 @@ pub mod common; #[tokio::test] async fn test_handshake() -> Result<()> { let server = TestServer::new().await; - let controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; controller.close().await?; Ok(()) } @@ -23,7 +23,7 @@ async fn test_handshake() -> Result<()> { async fn test_command() -> Result<()> { let server = TestServer::new().await; let runner = Runner::Shell("/bin/bash".into()); - let mut controller = Controller::new(&server.endpoint(), "", runner, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", runner, false).await?; let session = server .state() @@ -71,7 +71,7 @@ async fn test_ws_missing() -> Result<()> { async fn test_ws_basic() -> Result<()> { let server = TestServer::new().await; - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); tokio::spawn(async move { controller.run().await }); @@ -103,7 +103,7 @@ async fn test_ws_basic() -> Result<()> { async fn test_ws_resize() -> Result<()> { let server = TestServer::new().await; - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); tokio::spawn(async move { controller.run().await }); @@ -147,7 +147,7 @@ async fn test_ws_resize() -> Result<()> { async fn test_users_join() -> Result<()> { let server = TestServer::new().await; - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); tokio::spawn(async move { controller.run().await }); @@ -176,7 +176,7 @@ async fn test_users_join() -> Result<()> { async fn test_users_metadata() -> Result<()> { let server = TestServer::new().await; - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); tokio::spawn(async move { controller.run().await }); @@ -201,7 +201,7 @@ async fn test_users_metadata() -> Result<()> { async fn test_chat_messages() -> Result<()> { let server = TestServer::new().await; - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, false).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, false).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); tokio::spawn(async move { controller.run().await }); @@ -234,7 +234,7 @@ async fn test_read_write_permissions() -> Result<()> { let server = TestServer::new().await; // create controller with read-only mode enabled - let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo, true).await?; + let mut controller = Controller::new(&server.endpoint(), "", "", Runner::Echo, true).await?; let name = controller.name().to_owned(); let key = controller.encryption_key().to_owned(); let write_url = controller diff --git a/crates/sshx/src/controller.rs b/crates/sshx/src/controller.rs index 5eff7674..a2ecaaaa 100644 --- a/crates/sshx/src/controller.rs +++ b/crates/sshx/src/controller.rs @@ -50,6 +50,7 @@ impl Controller { pub async fn new( origin: &str, name: &str, + addr: &str, runner: Runner, enable_readers: bool, ) -> Result { @@ -84,6 +85,7 @@ impl Controller { origin: origin.into(), encrypted_zeros: encrypt.zeros().into(), name: name.into(), + addr: addr.into(), write_password_hash, }; let mut resp = client.open(req).await?.into_inner(); diff --git a/crates/sshx/src/main.rs b/crates/sshx/src/main.rs index d91f50ec..93f06b0e 100644 --- a/crates/sshx/src/main.rs +++ b/crates/sshx/src/main.rs @@ -27,6 +27,10 @@ struct Args { #[clap(long)] name: Option, + /// Sub path to use + #[clap(long, default_value = "")] + addr: String, + /// Enable read-only access mode - generates separate URLs for viewers and /// editors. #[clap(long)] @@ -90,10 +94,11 @@ async fn start(args: Args) -> Result<()> { }); let runner = Runner::Shell(shell.clone()); - let mut controller = Controller::new(&args.server, &name, runner, args.enable_readers).await?; + let mut controller = + Controller::new(&args.server, &name, &args.addr, runner, args.enable_readers).await?; if args.quiet { if let Some(write_url) = controller.write_url() { - println!("{}", write_url); + println!("{write_url}"); } else { println!("{}", controller.url()); }