Skip to content

Commit c1b3556

Browse files
authored
Add --name option, default user@hostname (#91)
* Add --name option, default user@hostname This appears in the title of the browser tab. If not specified, it will default to detecting the user and hostname of your machine using OS APIs. This change is backwards-compatible and forwards-compatible between old clients and old server versions. Resolves #90 and #85. * Remove console.log() * Fix clippy issue
1 parent ef30dde commit c1b3556

19 files changed

+104
-29
lines changed

Cargo.lock

+31-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["crates/*"]
33
resolver = "2"
44

55
[workspace.package]
6-
version = "0.2.3"
6+
version = "0.2.4"
77
authors = ["Eric Zhang <[email protected]>"]
88
license = "MIT"
99
description = "A secure web-based, collaborative terminal."
@@ -17,7 +17,7 @@ clap = { version = "4.4.2", features = ["derive", "env"] }
1717
prost = "0.12.0"
1818
rand = "0.8.5"
1919
serde = { version = "1.0.188", features = ["derive", "rc"] }
20-
sshx-core = { version = "0.2.3", path = "crates/sshx-core" }
20+
sshx-core = { version = "0.2.4", path = "crates/sshx-core" }
2121
tokio = { version = "1.32.0", features = ["full"] }
2222
tokio-stream = { version = "0.1.14", features = ["sync"] }
2323
tonic = { version = "0.10.0", features = ["tls", "tls-webpki-roots"] }

crates/sshx-core/proto/sshx.proto

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ message TerminalSize {
4040
message OpenRequest {
4141
string origin = 1; // Web origin of the server.
4242
bytes encrypted_zeros = 2; // Encrypted zero block, for client verification.
43+
string name = 3; // Name of the session (user@hostname).
4344
}
4445

4546
// Details of a newly-created sshx session.
@@ -101,6 +102,7 @@ message SerializedSession {
101102
map<uint32, SerializedShell> shells = 2;
102103
uint32 next_sid = 3;
103104
uint32 next_uid = 4;
105+
string name = 5;
104106
}
105107

106108
message SerializedShell {

crates/sshx-server/src/grpc.rs

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ impl SshxService for GrpcServer {
5555
None => {
5656
let metadata = Metadata {
5757
encrypted_zeros: request.encrypted_zeros,
58+
name: request.name,
5859
};
5960
self.0.insert(&name, Arc::new(Session::new(metadata)));
6061
}

crates/sshx-server/src/session.rs

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const SHELL_STORED_BYTES: u64 = 1 << 21; // 2 MiB
3030
pub struct Metadata {
3131
/// Used to validate that clients have the correct encryption key.
3232
pub encrypted_zeros: Bytes,
33+
34+
/// Name of the session (human-readable).
35+
pub name: String,
3336
}
3437

3538
/// In-memory state for a single sshx session.

crates/sshx-server/src/session/snapshot.rs

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl Session {
6161
.collect(),
6262
next_sid: ids.0 .0,
6363
next_uid: ids.1 .0,
64+
name: self.metadata().name.clone(),
6465
};
6566
let data = message.encode_to_vec();
6667
ensure!(data.len() < MAX_SNAPSHOT_SIZE, "snapshot too large");
@@ -73,6 +74,7 @@ impl Session {
7374
let message = SerializedSession::decode(&*data)?;
7475
let metadata = Metadata {
7576
encrypted_zeros: message.encrypted_zeros,
77+
name: message.name,
7678
};
7779

7880
let session = Self::new(metadata);

crates/sshx-server/src/web/protocol.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub struct WsUser {
4646
#[serde(rename_all = "camelCase")]
4747
pub enum WsServer {
4848
/// Initial server message, with the user's ID and session metadata.
49-
Hello(Uid),
49+
Hello(Uid, String),
5050
/// The user's authentication was invalid.
5151
InvalidAuth(),
5252
/// A snapshot of all current users in the session.

crates/sshx-server/src/web/socket.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,13 @@ async fn handle_socket(socket: &mut WebSocket, session: Arc<Session>) -> Result<
9090
})
9191
}
9292

93+
let metadata = session.metadata();
9394
let user_id = session.counter().next_uid();
9495
session.sync_now();
95-
send(socket, WsServer::Hello(user_id)).await?;
96+
send(socket, WsServer::Hello(user_id, metadata.name.clone())).await?;
9697

9798
match recv(socket).await? {
98-
Some(WsClient::Authenticate(bytes)) if bytes == session.metadata().encrypted_zeros => {}
99+
Some(WsClient::Authenticate(bytes)) if bytes == metadata.encrypted_zeros => {}
99100
_ => {
100101
send(socket, WsServer::InvalidAuth()).await?;
101102
return Ok(());

crates/sshx-server/tests/common/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ impl ClientSocket {
154154
let flush_task = async {
155155
while let Some(msg) = self.recv().await {
156156
match msg {
157-
WsServer::Hello(user_id) => self.user_id = user_id,
157+
WsServer::Hello(user_id, _) => self.user_id = user_id,
158158
WsServer::InvalidAuth() => panic!("invalid authentication"),
159159
WsServer::Users(users) => self.users = BTreeMap::from_iter(users),
160160
WsServer::UserDiff(id, maybe_user) => {

crates/sshx-server/tests/simple.rs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ async fn test_rpc() -> Result<()> {
1414
let req = OpenRequest {
1515
origin: "sshx.io".into(),
1616
encrypted_zeros: Encrypt::new("").zeros().into(),
17+
name: String::new(),
1718
};
1819
let resp = client.open(req).await?;
1920
assert!(!resp.into_inner().name.is_empty());

crates/sshx-server/tests/snapshot.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub mod common;
1616
async fn test_basic_restore() -> Result<()> {
1717
let server = TestServer::new().await;
1818

19-
let mut controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
19+
let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
2020
let name = controller.name().to_owned();
2121
let key = controller.encryption_key().to_owned();
2222
tokio::spawn(async move { controller.run().await });

crates/sshx-server/tests/with_client.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub mod common;
1414
#[tokio::test]
1515
async fn test_handshake() -> Result<()> {
1616
let server = TestServer::new().await;
17-
let controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
17+
let controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
1818
controller.close().await?;
1919
Ok(())
2020
}
@@ -23,7 +23,7 @@ async fn test_handshake() -> Result<()> {
2323
async fn test_command() -> Result<()> {
2424
let server = TestServer::new().await;
2525
let runner = Runner::Shell("/bin/bash".into());
26-
let mut controller = Controller::new(&server.endpoint(), runner).await?;
26+
let mut controller = Controller::new(&server.endpoint(), "", runner).await?;
2727

2828
let session = server
2929
.state()
@@ -69,7 +69,7 @@ async fn test_ws_missing() -> Result<()> {
6969
async fn test_ws_basic() -> Result<()> {
7070
let server = TestServer::new().await;
7171

72-
let mut controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
72+
let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
7373
let name = controller.name().to_owned();
7474
let key = controller.encryption_key().to_owned();
7575
tokio::spawn(async move { controller.run().await });
@@ -101,7 +101,7 @@ async fn test_ws_basic() -> Result<()> {
101101
async fn test_ws_resize() -> Result<()> {
102102
let server = TestServer::new().await;
103103

104-
let mut controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
104+
let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
105105
let name = controller.name().to_owned();
106106
let key = controller.encryption_key().to_owned();
107107
tokio::spawn(async move { controller.run().await });
@@ -145,7 +145,7 @@ async fn test_ws_resize() -> Result<()> {
145145
async fn test_users_join() -> Result<()> {
146146
let server = TestServer::new().await;
147147

148-
let mut controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
148+
let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
149149
let name = controller.name().to_owned();
150150
let key = controller.encryption_key().to_owned();
151151
tokio::spawn(async move { controller.run().await });
@@ -174,7 +174,7 @@ async fn test_users_join() -> Result<()> {
174174
async fn test_users_metadata() -> Result<()> {
175175
let server = TestServer::new().await;
176176

177-
let mut controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
177+
let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
178178
let name = controller.name().to_owned();
179179
let key = controller.encryption_key().to_owned();
180180
tokio::spawn(async move { controller.run().await });
@@ -199,7 +199,7 @@ async fn test_users_metadata() -> Result<()> {
199199
async fn test_chat_messages() -> Result<()> {
200200
let server = TestServer::new().await;
201201

202-
let mut controller = Controller::new(&server.endpoint(), Runner::Echo).await?;
202+
let mut controller = Controller::new(&server.endpoint(), "", Runner::Echo).await?;
203203
let name = controller.name().to_owned();
204204
let key = controller.encryption_key().to_owned();
205205
tokio::spawn(async move { controller.run().await });

crates/sshx/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ tokio-stream.workspace = true
2626
tonic.workspace = true
2727
tracing.workspace = true
2828
tracing-subscriber.workspace = true
29+
whoami = { version = "1.5.1", default-features = false }

crates/sshx/src/controller.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,21 @@ pub struct Controller {
4646

4747
impl Controller {
4848
/// Construct a new controller, connecting to the remote server.
49-
pub async fn new(origin: &str, runner: Runner) -> Result<Self> {
49+
pub async fn new(origin: &str, name: &str, runner: Runner) -> Result<Self> {
5050
debug!(%origin, "connecting to server");
5151
let encryption_key = rand_alphanumeric(14); // 83.3 bits of entropy
5252

53-
let encryption_key2 = encryption_key.clone();
54-
let kdf_task = task::spawn_blocking(move || Encrypt::new(&encryption_key2));
55-
53+
let kdf_task = {
54+
let encryption_key = encryption_key.clone();
55+
task::spawn_blocking(move || Encrypt::new(&encryption_key))
56+
};
5657
let mut client = Self::connect(origin).await?;
5758
let encrypt = kdf_task.await?;
5859

5960
let req = OpenRequest {
6061
origin: origin.into(),
6162
encrypted_zeros: encrypt.zeros().into(),
63+
name: name.into(),
6264
};
6365
let mut resp = client.open(req).await?.into_inner();
6466
resp.url = resp.url + "#" + &encryption_key;

crates/sshx/src/main.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ struct Args {
2222
/// Quiet mode, only prints the URL to stdout.
2323
#[clap(short, long)]
2424
quiet: bool,
25+
26+
/// Session name displayed in the title (defaults to user@hostname).
27+
#[clap(long)]
28+
name: Option<String>,
2529
}
2630

2731
fn print_greeting(shell: &str, controller: &Controller) {
@@ -52,8 +56,19 @@ async fn start(args: Args) -> Result<()> {
5256
None => get_default_shell().await,
5357
};
5458

59+
let name = args.name.unwrap_or_else(|| {
60+
let mut name = whoami::username();
61+
if let Ok(host) = whoami::fallible::hostname() {
62+
// Trim domain information like .lan or .local
63+
let host = host.split('.').next().unwrap_or(&host);
64+
name += "@";
65+
name += host;
66+
}
67+
name
68+
});
69+
5570
let runner = Runner::Shell(shell.clone());
56-
let mut controller = Controller::new(&args.server, runner).await?;
71+
let mut controller = Controller::new(&args.server, &name, runner).await?;
5772
if args.quiet {
5873
println!("{}", controller.url());
5974
} else {

src/lib/Session.svelte

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
<script lang="ts">
2-
import { onDestroy, onMount, tick, beforeUpdate, afterUpdate } from "svelte";
2+
import {
3+
onDestroy,
4+
onMount,
5+
tick,
6+
beforeUpdate,
7+
afterUpdate,
8+
createEventDispatcher,
9+
} from "svelte";
310
import { fade } from "svelte/transition";
411
import { debounce, throttle } from "lodash-es";
512
@@ -24,6 +31,8 @@
2431
2532
export let id: string;
2633
34+
const dispatch = createEventDispatcher<{ receiveName: string }>();
35+
2736
// The magic numbers "left" and "top" are used to approximately center the
2837
// terminal at the time that it is first created.
2938
//
@@ -128,7 +137,8 @@
128137
srocket = new Srocket<WsServer, WsClient>(`/api/s/${id}`, {
129138
onMessage(message) {
130139
if (message.hello) {
131-
userId = message.hello;
140+
userId = message.hello[0];
141+
dispatch("receiveName", message.hello[1]);
132142
makeToast({
133143
kind: "success",
134144
message: `Connected to the server.`,

src/lib/protocol.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type WsUser = {
1818

1919
/** Server message type, see the Rust version. */
2020
export type WsServer = {
21-
hello?: Uid;
21+
hello?: [Uid, string];
2222
invalidAuth?: [];
2323
users?: [Uid, WsUser][];
2424
userDiff?: [Uid, WsUser | null];

0 commit comments

Comments
 (0)