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
13pub enum SqlPermission {
15 Read(StAccess),
24 Write,
26 ExceedRowLimit,
28 BypassRLS,
31}
32
33pub trait SqlAuthorization {
35 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
45pub type SqlPermissions = Arc<dyn SqlAuthorization + Send + Sync + 'static>;
47
48fn 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#[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 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 pub fn for_testing() -> Self {
108 Self::new(Identity::__dummy(), Identity::__dummy())
109 }
110}
111
112#[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 pub const ZERO: Self = Self::from_u256(u256::ZERO);
150
151 pub const ONE: Self = Self::from_u256(u256::ONE);
153
154 pub const fn from_byte_array(bytes: [u8; 32]) -> Self {
158 Self::from_u256(u256::from_le(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
161 }
162
163 pub const fn from_be_byte_array(bytes: [u8; 32]) -> Self {
173 Self::from_u256(u256::from_be(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
176 }
177
178 pub const fn from_u256(__identity__: u256) -> Self {
180 Self { __identity__ }
181 }
182
183 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 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 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 Identity::from_be_byte_array(final_bytes)
217 }
218
219 pub fn to_byte_array(&self) -> [u8; 32] {
221 self.__identity__.to_le_bytes()
222 }
223
224 pub fn to_be_byte_array(&self) -> [u8; 32] {
226 self.__identity__.to_be_bytes()
227 }
228
229 pub fn to_hex(&self) -> HexString<32> {
231 spacetimedb_sats::hex::encode(&self.to_be_byte_array())
232 }
233
234 pub fn abbreviate(&self) -> [u8; 8] {
237 self.to_be_byte_array()[..8].try_into().unwrap()
238 }
239
240 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 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}