Skip to main content

pubky_common/
session.rs

1//! Pubky homeserver session types.
2
3use postcard::{from_bytes, to_allocvec};
4use serde::{Deserialize, Serialize};
5
6extern crate alloc;
7use alloc::vec::Vec;
8
9use crate::{
10    capabilities::{Capabilities, Capability},
11    crypto::PublicKey,
12    timestamp::Timestamp,
13};
14
15/// Cookie-specific session record with binary (postcard) serialization.
16///
17/// This is the legacy wire format used by the cookie auth flow. It carries
18/// deprecated fields (`name`, `user_agent`) and a `version` byte for backward
19/// compatibility with existing clients.
20///
21/// When the cookie flow is retired, this struct can be deleted.
22#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
23pub struct CookieSessionRecord {
24    version: usize,
25    public_key: PublicKey,
26    created_at: u64,
27    /// Deprecated. Will always be empty.
28    name: String,
29    /// Deprecated. Will always be empty.
30    user_agent: String,
31    capabilities: Vec<Capability>,
32}
33
34impl CookieSessionRecord {
35    /// Create a new cookie session record.
36    pub fn new(
37        public_key: &PublicKey,
38        capabilities: Capabilities,
39        user_agent: Option<String>,
40    ) -> Self {
41        Self {
42            version: 0,
43            public_key: public_key.clone(),
44            created_at: Timestamp::now().as_u64(),
45            capabilities: capabilities.into(),
46            user_agent: user_agent.as_deref().unwrap_or("").to_string(),
47            name: user_agent.as_deref().unwrap_or("").to_string(),
48        }
49    }
50
51    // === Getters ===
52
53    /// Returns the public_key of this session authorizes for.
54    pub fn public_key(&self) -> &PublicKey {
55        &self.public_key
56    }
57
58    /// Returns the capabilities this session provide on this session's public_key's resources.
59    pub fn capabilities(&self) -> &[Capability] {
60        &self.capabilities
61    }
62
63    /// Returns the timestamp when this session was created.
64    pub fn created_at(&self) -> u64 {
65        self.created_at
66    }
67
68    // === Setters ===
69
70    /// Set the timestamp when this session was created.
71    pub fn set_created_at(&mut self, created_at: u64) -> &mut Self {
72        self.created_at = created_at;
73        self
74    }
75
76    /// Set this session's capabilities.
77    pub fn set_capabilities(&mut self, capabilities: Capabilities) -> &mut Self {
78        self.capabilities = capabilities.into();
79
80        self
81    }
82
83    // === Public Methods ===
84
85    /// Serialize this session to its canonical binary representation.
86    pub fn serialize(&self) -> Vec<u8> {
87        to_allocvec(self).expect("CookieSessionRecord::serialize")
88    }
89
90    /// Deserialize this session from its canonical binary representation.
91    pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
92        if bytes.is_empty() {
93            return Err(Error::EmptyPayload);
94        }
95
96        if bytes[0] > 0 {
97            return Err(Error::UnknownVersion);
98        }
99
100        Ok(from_bytes(bytes)?)
101    }
102
103    // TODO: add `can_read()`, `can_write()` and `is_root()` methods
104}
105
106#[derive(thiserror::Error, Debug, PartialEq)]
107/// Error deserializing a [CookieSessionRecord].
108pub enum Error {
109    #[error("Empty payload")]
110    /// Empty payload
111    EmptyPayload,
112    #[error("Unknown version")]
113    /// Unknown version
114    UnknownVersion,
115    #[error(transparent)]
116    /// Error parsing the binary representation.
117    Parsing(#[from] postcard::Error),
118}
119
120#[cfg(test)]
121mod tests {
122    use crate::{capabilities::Capability, crypto::Keypair};
123
124    use super::*;
125
126    #[test]
127    fn serialize() {
128        let keypair = Keypair::from_secret(&[0; 32]);
129        let public_key = keypair.public_key();
130        let capabilities = Capabilities::builder().cap(Capability::root()).finish();
131
132        let session = CookieSessionRecord {
133            user_agent: "foo".to_string(),
134            capabilities: capabilities.into(),
135            created_at: 0,
136            public_key,
137            version: 0,
138            name: "".to_string(),
139        };
140
141        let serialized = session.serialize();
142
143        assert_eq!(
144            serialized,
145            [
146                0, 59, 106, 39, 188, 206, 182, 164, 45, 98, 163, 168, 208, 42, 111, 13, 115, 101,
147                50, 21, 119, 29, 226, 67, 166, 58, 192, 72, 161, 139, 89, 218, 41, 0, 0, 3, 102,
148                111, 111, 1, 4, 47, 58, 114, 119
149            ]
150        );
151
152        let deserialized = CookieSessionRecord::deserialize(&serialized).unwrap();
153
154        assert_eq!(deserialized, session)
155    }
156
157    #[test]
158    fn deserialize() {
159        let result = CookieSessionRecord::deserialize(&[]);
160
161        assert_eq!(result, Err(Error::EmptyPayload));
162    }
163}