| 1 | use serde::{Deserialize, Serialize}; |
| 2 | |
| 3 | use uuid::Uuid; |
| 4 | |
| 5 | /// The canonical identifier for a shared session. |
| 6 | /// A [`SessionId`] on its own cannot be used to access |
| 7 | /// a shared session; you need the corresponding [`SessionSecret`]. |
| 8 | /// TODO: consider making the internal type a plain old String. |
| 9 | #[derive(Debug, Hash, Serialize, Deserialize, Eq, PartialEq, Clone, Copy)] |
| 10 | #[serde(transparent)] |
| 11 | pub struct SessionId(Uuid); |
| 12 | impl SessionId { |
| 13 | #[allow(clippy::new_without_default)] |
| 14 | pub fn new() -> Self { |
| 15 | Self(Uuid::new_v4()) |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | impl std::fmt::Display for SessionId { |
| 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 21 | write!(f, "{}", self.0) |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | /// The `warp` server framework uses [`FromStr`] to deserialize |
| 26 | /// the string from the route. |
| 27 | impl std::str::FromStr for SessionId { |
| 28 | type Err = uuid::Error; |
| 29 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 30 | Uuid::from_str(s).map(SessionId) |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | /// The secret for a shared session. |
| 35 | /// A shared session cannot be accessed without its secret. |
| 36 | /// The client should treat this as some opaque string. |
| 37 | #[derive(Hash, Serialize, Deserialize, Eq, PartialEq, Clone, Default)] |
| 38 | #[serde(transparent)] |
| 39 | pub struct SessionSecret(String); |
| 40 | impl SessionSecret { |
| 41 | #[allow(clippy::new_without_default)] |
| 42 | pub fn new() -> Self { |
| 43 | Self(Uuid::new_v4().to_string()) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// Override the Display impl for the secret to return a mask. |
| 48 | /// This makes it harder to leak the secret by accident. |
| 49 | impl std::fmt::Display for SessionSecret { |
| 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 51 | write!(f, "***") |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | /// Override the Debug impl for the secret to return a mask. |
| 56 | /// This makes it harder to leak the secret by accident. |
| 57 | impl std::fmt::Debug for SessionSecret { |
| 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 59 | write!(f, "***") |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | /// The `warp` server framework uses [`FromStr`] to deserialize |
| 64 | /// the string from the route. |
| 65 | impl std::str::FromStr for SessionSecret { |
| 66 | type Err = core::convert::Infallible; |
| 67 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 68 | String::from_str(s).map(SessionSecret) |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | /// The parameters needed to attempt to join a shared session. |
| 73 | /// This is different from [`viewer::InitPayload`] which |
| 74 | /// is the state that a viewer must pass up _after_ successfully |
| 75 | /// joining a shared session. |
| 76 | #[derive(Clone)] |
| 77 | pub struct JoinSessionLinkArgs { |
| 78 | pub session_id: SessionId, |
| 79 | pub session_secret: SessionSecret, |
| 80 | } |
| 81 | |
| 82 | impl JoinSessionLinkArgs { |
| 83 | // TODO: ideally, the protocol should just generate the full |
| 84 | // link for the client to consume. This will make more sense |
| 85 | // once we move away from app URIs. |
| 86 | pub fn to_join_route(&self) -> String { |
| 87 | format!( |
| 88 | "/sessions/join/{}?pwd={}", |
| 89 | self.session_id, |
| 90 | self.secret_to_string(), |
| 91 | ) |
| 92 | } |
| 93 | |
| 94 | /// Returns the [`SessionSecret`] as a [`String`] for joining purposes. |
| 95 | pub fn secret_to_string(&self) -> String { |
| 96 | // We can't use the [`SessionSecret`]'s display because it's overriden to be masked. |
| 97 | self.session_secret.0.to_string() |
| 98 | } |
| 99 | } |