spacetimedb_lib/
identity.rs

1use crate::db::auth::StAccess;
2use crate::from_hex_pad;
3use blake3;
4use core::mem;
5use spacetimedb_bindings_macro::{Deserialize, Serialize};
6use spacetimedb_sats::hex::HexString;
7use spacetimedb_sats::{impl_st, u256, AlgebraicType, AlgebraicValue};
8use std::sync::Arc;
9use std::{fmt, str::FromStr};
10
11pub type RequestId = u32;
12
13/// Set of permissions the SQL engine may ask for.
14pub enum SqlPermission {
15    /// Read permissions given the [StAccess] of a table.
16    ///
17    /// [StAccess] must be passed in order to allow external implementations
18    /// to fail compilation should the [StAccess] enum ever gain additional
19    /// variants. Implementations should always do an exhaustive match thus.
20    ///
21    /// [SqlAuthorization::has_sql_permission] must return true if
22    /// [StAccess::Public].
23    Read(StAccess),
24    /// Write access, i.e. executing DML.
25    Write,
26    /// If granted, no row limit checks will be performed for subscription queries.
27    ExceedRowLimit,
28    /// RLS does not apply to database owners (for some definition of owner).
29    /// If the subject qualifies as an owner, the permission should be granted.
30    BypassRLS,
31}
32
33/// Types than can grant or deny [SqlPermission]s.
34pub trait SqlAuthorization {
35    /// Returns `true` if permission `p` is granted, `false` otherwise.
36    fn has_sql_permission(&self, p: SqlPermission) -> bool;
37}
38
39impl<T: Fn(SqlPermission) -> bool> SqlAuthorization for T {
40    fn has_sql_permission(&self, p: SqlPermission) -> bool {
41        self(p)
42    }
43}
44
45/// [SqlAuthorization] trait object.
46pub type SqlPermissions = Arc<dyn SqlAuthorization + Send + Sync + 'static>;
47
48/// The legacy permissions (sans "teams") grant everything if the owner is
49/// equal to the caller.
50fn owner_permissions(owner: Identity, caller: Identity) -> SqlPermissions {
51    let is_owner = owner == caller;
52    Arc::new(move |p| match p {
53        SqlPermission::Read(access) => match access {
54            StAccess::Public => true,
55            StAccess::Private => is_owner,
56        },
57        _ => is_owner,
58    })
59}
60
61/// Authorization for SQL operations (queries, DML, subscription queries).
62#[derive(Clone)]
63pub struct AuthCtx {
64    caller: Identity,
65    permissions: SqlPermissions,
66}
67
68impl AuthCtx {
69    pub fn new(owner: Identity, caller: Identity) -> Self {
70        Self::with_permissions(caller, owner_permissions(owner, caller))
71    }
72
73    pub fn with_permissions(caller: Identity, permissions: SqlPermissions) -> Self {
74        Self { caller, permissions }
75    }
76
77    /// For when the owner == caller
78    pub fn for_current(owner: Identity) -> Self {
79        Self::new(owner, owner)
80    }
81
82    pub fn has_permission(&self, p: SqlPermission) -> bool {
83        self.permissions.has_sql_permission(p)
84    }
85
86    pub fn has_read_access(&self, table_access: StAccess) -> bool {
87        self.has_permission(SqlPermission::Read(table_access))
88    }
89
90    pub fn has_write_access(&self) -> bool {
91        self.has_permission(SqlPermission::Write)
92    }
93
94    pub fn exceed_row_limit(&self) -> bool {
95        self.has_permission(SqlPermission::ExceedRowLimit)
96    }
97
98    pub fn bypass_rls(&self) -> bool {
99        self.has_permission(SqlPermission::BypassRLS)
100    }
101
102    pub fn caller(&self) -> Identity {
103        self.caller
104    }
105
106    /// WARNING: Use this only for simple test were the `auth` don't matter
107    pub fn for_testing() -> Self {
108        Self::new(Identity::__dummy(), Identity::__dummy())
109    }
110}
111
112/// An `Identity` for something interacting with the database.
113///
114/// <!-- TODO: docs for OpenID stuff. -->
115///
116/// An `Identity` is a 256-bit unsigned integer. These are encoded in various ways.
117/// - In JSON, an `Identity` is represented as a hexadecimal number wrapped in a string, `"0x[64 hex characters]"`.
118/// - In BSATN, an `Identity` is represented as a LITTLE-ENDIAN number 32 bytes long.
119/// - In memory, an `Identity` is stored as a 256-bit number with the endianness of the host system.
120///
121/// If you are manually converting a hexadecimal string to a byte array like so:
122/// ```ignore
123/// "0xb0b1b2..."
124/// ->
125/// [0xb0, 0xb1, 0xb2, ...]
126/// ```
127/// Make sure you call `Identity::from_be_byte_array` and NOT `Identity::from_byte_array`.
128/// The standard way of writing hexadecimal numbers follows a big-endian convention, if you
129/// index the characters in written text in increasing order from left to right.
130#[derive(Default, Eq, PartialEq, PartialOrd, Ord, Clone, Copy, Hash, Serialize, Deserialize)]
131pub struct Identity {
132    __identity__: u256,
133}
134
135impl_st!([] Identity, AlgebraicType::identity());
136
137#[cfg(feature = "memory-usage")]
138impl spacetimedb_memory_usage::MemoryUsage for Identity {}
139
140#[cfg(feature = "metrics_impls")]
141impl spacetimedb_metrics::typed_prometheus::AsPrometheusLabel for Identity {
142    fn as_prometheus_str(&self) -> impl AsRef<str> + '_ {
143        self.to_hex()
144    }
145}
146
147impl Identity {
148    /// The 0x0 `Identity`
149    pub const ZERO: Self = Self::from_u256(u256::ZERO);
150
151    /// The 0x1 `Identity`
152    pub const ONE: Self = Self::from_u256(u256::ONE);
153
154    /// Create an `Identity` from a LITTLE-ENDIAN byte array.
155    ///
156    /// If you are parsing an `Identity` from a string, you probably want `from_be_byte_array` instead.
157    pub const fn from_byte_array(bytes: [u8; 32]) -> Self {
158        // SAFETY: The transmute is an implementation of `u256::from_le_bytes`,
159        // but works in a const context.
160        Self::from_u256(u256::from_le(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
161    }
162
163    /// Create an `Identity` from a BIG-ENDIAN byte array.
164    ///
165    /// This method is the correct choice if you have converted the bytes of a hexadecimal-formatted `Identity`
166    /// to a byte array in the following way:
167    /// ```ignore
168    /// "0xb0b1b2..."
169    /// ->
170    /// [0xb0, 0xb1, 0xb2, ...]
171    /// ```
172    pub const fn from_be_byte_array(bytes: [u8; 32]) -> Self {
173        // SAFETY: The transmute is an implementation of `u256::from_be_bytes`,
174        // but works in a const context.
175        Self::from_u256(u256::from_be(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
176    }
177
178    /// Converts `__identity__: u256` to `Identity`.
179    pub const fn from_u256(__identity__: u256) -> Self {
180        Self { __identity__ }
181    }
182
183    /// Converts this identity to an `u256`.
184    pub const fn to_u256(&self) -> u256 {
185        self.__identity__
186    }
187
188    #[doc(hidden)]
189    pub fn __dummy() -> Self {
190        Self::ZERO
191    }
192
193    /// Derives an identity from a [JWT] `issuer` and a `subject`.
194    ///
195    /// [JWT]: https://en.wikipedia.org/wiki/JSON_Web_Token
196    pub fn from_claims(issuer: &str, subject: &str) -> Self {
197        let input = format!("{issuer}|{subject}");
198        let first_hash = blake3::hash(input.as_bytes());
199        let id_hash = &first_hash.as_bytes()[..26];
200        let mut checksum_input = [0u8; 28];
201        // TODO: double check this gets the right number...
202        checksum_input[2..].copy_from_slice(id_hash);
203        checksum_input[0] = 0xc2;
204        checksum_input[1] = 0x00;
205        let checksum_hash = &blake3::hash(&checksum_input);
206
207        let mut final_bytes = [0u8; 32];
208        final_bytes[0] = 0xc2;
209        final_bytes[1] = 0x00;
210        final_bytes[2..6].copy_from_slice(&checksum_hash.as_bytes()[..4]);
211        final_bytes[6..].copy_from_slice(id_hash);
212
213        // We want the leading two bytes of the Identity to be `c200` when formatted.
214        // This means that these should be the MOST significant bytes.
215        // This corresponds to a BIG-ENDIAN byte order of our buffer above.
216        Identity::from_be_byte_array(final_bytes)
217    }
218
219    /// Returns this `Identity` as a byte array.
220    pub fn to_byte_array(&self) -> [u8; 32] {
221        self.__identity__.to_le_bytes()
222    }
223
224    /// Convert this `Identity` to a BIG-ENDIAN byte array.
225    pub fn to_be_byte_array(&self) -> [u8; 32] {
226        self.__identity__.to_be_bytes()
227    }
228
229    /// Convert this `Identity` to a hexadecimal string.
230    pub fn to_hex(&self) -> HexString<32> {
231        spacetimedb_sats::hex::encode(&self.to_be_byte_array())
232    }
233
234    /// Extract the first 8 bytes of this `Identity` as if it was stored in BIG-ENDIAN
235    /// format. (That is, the most significant bytes.)
236    pub fn abbreviate(&self) -> [u8; 8] {
237        self.to_be_byte_array()[..8].try_into().unwrap()
238    }
239
240    /// Extract the first 16 characters of this `Identity`'s hexadecimal representation.
241    pub fn to_abbreviated_hex(&self) -> HexString<8> {
242        spacetimedb_sats::hex::encode(&self.abbreviate())
243    }
244
245    pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, hex::FromHexError> {
246        hex::FromHex::from_hex(hex)
247    }
248}
249
250impl fmt::Display for Identity {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        f.pad(&self.to_hex())
253    }
254}
255
256impl fmt::Debug for Identity {
257    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258        f.debug_tuple("Identity").field(&self.to_hex()).finish()
259    }
260}
261
262impl hex::FromHex for Identity {
263    type Error = hex::FromHexError;
264
265    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
266        from_hex_pad(hex).map(Identity::from_be_byte_array)
267    }
268}
269
270impl FromStr for Identity {
271    type Err = <Self as hex::FromHex>::Error;
272
273    fn from_str(s: &str) -> Result<Self, Self::Err> {
274        Self::from_hex(s)
275    }
276}
277
278impl From<Identity> for AlgebraicValue {
279    fn from(value: Identity) -> Self {
280        AlgebraicValue::product([value.to_u256().into()])
281    }
282}
283
284#[cfg(feature = "serde")]
285impl serde::Serialize for Identity {
286    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
287        spacetimedb_sats::ser::serde::serialize_to(&self.to_be_byte_array(), serializer)
288    }
289}
290
291#[cfg(feature = "serde")]
292impl<'de> serde::Deserialize<'de> for Identity {
293    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
294        let arr = spacetimedb_sats::de::serde::deserialize_from(deserializer)?;
295        Ok(Identity::from_be_byte_array(arr))
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    use proptest::prelude::*;
303    use proptest::string::string_regex;
304    use spacetimedb_sats::{de::serde::DeserializeWrapper, ser::serde::SerializeWrapper, GroundSpacetimeType as _};
305
306    #[test]
307    fn identity_is_special() {
308        assert!(Identity::get_type().is_special());
309    }
310
311    #[test]
312    fn identity_json_serialization_big_endian() {
313        let id = Identity::from_be_byte_array([
314            0xff, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
315            28, 29, 30, 31,
316        ]);
317
318        let hex = id.to_hex();
319        assert!(
320            hex.as_str().starts_with("ff01"),
321            "expected {hex:?} to start with \"ff01\""
322        );
323
324        let json1 = serde_json::to_string(&id).unwrap();
325        let json2 = serde_json::to_string(SerializeWrapper::from_ref(&id)).unwrap();
326
327        assert!(
328            json1.contains(hex.as_str()),
329            "expected {json1} to contain {hex} but it didn't"
330        );
331        assert!(
332            json2.contains(hex.as_str()),
333            "expected {json2} to contain {hex} but it didn't"
334        );
335    }
336
337    /// Make sure the checksum is valid.
338    fn validate_checksum(id: &[u8; 32]) -> bool {
339        let checksum_input = &id[6..];
340        let mut checksum_input_with_prefix = [0u8; 28];
341        checksum_input_with_prefix[2..].copy_from_slice(checksum_input);
342        checksum_input_with_prefix[0] = 0xc2;
343        checksum_input_with_prefix[1] = 0x00;
344        let checksum_hash = &blake3::hash(&checksum_input_with_prefix);
345        checksum_hash.as_bytes()[0..4] == id[2..6]
346    }
347
348    proptest! {
349        #[test]
350        fn identity_conversions(w0: u128, w1: u128) {
351            let v = Identity::from_u256(u256::from_words(w0, w1));
352
353            prop_assert_eq!(Identity::from_byte_array(v.to_byte_array()), v);
354            prop_assert_eq!(Identity::from_be_byte_array(v.to_be_byte_array()), v);
355            prop_assert_eq!(Identity::from_hex(v.to_hex()).unwrap(), v);
356
357            let de1: Identity = serde_json::from_str(&serde_json::to_string(&v).unwrap()).unwrap();
358            prop_assert_eq!(de1, v);
359            let DeserializeWrapper(de2): DeserializeWrapper<Identity> = serde_json::from_str(&serde_json::to_string(SerializeWrapper::from_ref(&v)).unwrap()).unwrap();
360            prop_assert_eq!(de2, v);
361        }
362
363        #[test]
364        fn from_claims_formats_correctly(s1 in string_regex(r".{3,5}").unwrap(), s2 in string_regex(r".{3,5}").unwrap()) {
365            let id = Identity::from_claims(&s1, &s2);
366            prop_assert!(id.to_hex().starts_with("c200"));
367            prop_assert!(validate_checksum(&id.to_be_byte_array()));
368        }
369    }
370}