Skip to content

Commit 59ec2a0

Browse files
committed
Remove spacetimedb-core as a dep of cli
1 parent 6cf0e7b commit 59ec2a0

File tree

16 files changed

+191
-73
lines changed

16 files changed

+191
-73
lines changed

Cargo.lock

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

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"crates/auth",
34
"crates/bench",
45
"crates/bindings-sys",
56
"crates/bindings",
@@ -92,6 +93,7 @@ rust-version = "1.78.0"
9293

9394
[workspace.dependencies]
9495
spacetimedb = { path = "crates/bindings", version = "1.0.0-rc3" }
96+
spacetimedb-auth = { path = "crates/auth", version = "1.0.0-rc3" }
9597
spacetimedb-bindings-macro = { path = "crates/bindings-macro", version = "1.0.0-rc3" }
9698
spacetimedb-bindings-sys = { path = "crates/bindings-sys", version = "1.0.0-rc3" }
9799
spacetimedb-cli = { path = "crates/cli", version = "1.0.0-rc3" }
@@ -181,6 +183,7 @@ itertools = "0.12"
181183
itoa = "1"
182184
jsonwebtoken = { package = "spacetimedb-jsonwebtoken", version = "9.3.0" }
183185
junction = "1"
186+
jwks = { package = "spacetimedb-jwks", version = "0.1.3" }
184187
lazy_static = "1.4.0"
185188
log = "0.4.17"
186189
memchr = "2"

crates/auth/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "spacetimedb-auth"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
7+
[dependencies]
8+
spacetimedb-lib.workspace = true
9+
10+
anyhow.workspace = true
11+
serde.workspace = true
12+
serde_with.workspace = true
13+
jsonwebtoken.workspace = true

crates/auth/src/identity.rs

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
pub use jsonwebtoken::errors::Error as JwtError;
2+
pub use jsonwebtoken::errors::ErrorKind as JwtErrorKind;
3+
pub use jsonwebtoken::{DecodingKey, EncodingKey};
4+
use serde::{Deserialize, Serialize};
5+
use spacetimedb_lib::Identity;
6+
use std::time::SystemTime;
7+
8+
// These are the claims that can be attached to a request/connection.
9+
#[serde_with::serde_as]
10+
#[derive(Debug, Serialize, Deserialize)]
11+
pub struct SpacetimeIdentityClaims {
12+
#[serde(rename = "hex_identity")]
13+
pub identity: Identity,
14+
#[serde(rename = "sub")]
15+
pub subject: String,
16+
#[serde(rename = "iss")]
17+
pub issuer: String,
18+
#[serde(rename = "aud")]
19+
pub audience: Vec<String>,
20+
21+
/// The unix timestamp the token was issued at
22+
#[serde_as(as = "serde_with::TimestampSeconds")]
23+
pub iat: SystemTime,
24+
#[serde_as(as = "Option<serde_with::TimestampSeconds>")]
25+
pub exp: Option<SystemTime>,
26+
}
27+
28+
// IncomingClaims are from the token we receive from the client.
29+
// The signature should be verified already, but further validation is needed to have a SpacetimeIdentityClaims2.
30+
#[serde_with::serde_as]
31+
#[derive(Debug, Serialize, Deserialize)]
32+
pub struct IncomingClaims {
33+
#[serde(rename = "hex_identity")]
34+
pub identity: Option<Identity>,
35+
#[serde(rename = "sub")]
36+
pub subject: String,
37+
#[serde(rename = "iss")]
38+
pub issuer: String,
39+
#[serde(rename = "aud", default)]
40+
pub audience: Vec<String>,
41+
42+
/// The unix timestamp the token was issued at
43+
#[serde_as(as = "serde_with::TimestampSeconds")]
44+
pub iat: SystemTime,
45+
#[serde_as(as = "Option<serde_with::TimestampSeconds>")]
46+
pub exp: Option<SystemTime>,
47+
}
48+
49+
impl TryInto<SpacetimeIdentityClaims> for IncomingClaims {
50+
type Error = anyhow::Error;
51+
52+
fn try_into(self) -> anyhow::Result<SpacetimeIdentityClaims> {
53+
// The issuer and subject must be less than 128 bytes.
54+
if self.issuer.len() > 128 {
55+
return Err(anyhow::anyhow!("Issuer too long: {:?}", self.issuer));
56+
}
57+
if self.subject.len() > 128 {
58+
return Err(anyhow::anyhow!("Subject too long: {:?}", self.subject));
59+
}
60+
// The issuer and subject must be non-empty.
61+
if self.issuer.is_empty() {
62+
return Err(anyhow::anyhow!("Issuer empty"));
63+
}
64+
if self.subject.is_empty() {
65+
return Err(anyhow::anyhow!("Subject empty"));
66+
}
67+
68+
let computed_identity = Identity::from_claims(&self.issuer, &self.subject);
69+
// If an identity is provided, it must match the computed identity.
70+
if let Some(token_identity) = self.identity {
71+
if token_identity != computed_identity {
72+
return Err(anyhow::anyhow!(
73+
"Identity mismatch: token identity {:?} does not match computed identity {:?}",
74+
token_identity,
75+
computed_identity,
76+
));
77+
}
78+
}
79+
80+
Ok(SpacetimeIdentityClaims {
81+
identity: computed_identity,
82+
subject: self.subject,
83+
issuer: self.issuer,
84+
audience: self.audience,
85+
iat: self.iat,
86+
exp: self.exp,
87+
})
88+
}
89+
}

crates/auth/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod identity;

crates/cli/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ bench = false
1818
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1919

2020
[dependencies]
21+
spacetimedb-auth.workspace = true
2122
spacetimedb-client-api-messages.workspace = true
22-
spacetimedb-core.workspace = true
23-
spacetimedb-data-structures.workspace = true
23+
spacetimedb-data-structures = { workspace = true, features = ["serde"] }
2424
spacetimedb-fs-utils.workspace = true
2525
spacetimedb-lib.workspace = true
2626
spacetimedb-paths.workspace = true

crates/cli/src/config.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::errors::CliError;
22
use crate::util::{contains_protocol, host_or_url_to_host_and_protocol};
33
use anyhow::Context;
44
use jsonwebtoken::DecodingKey;
5-
use spacetimedb::config::{set_opt_value, set_table_opt_value};
65
use spacetimedb_fs_utils::atomic_write;
76
use spacetimedb_paths::cli::CliTomlPath;
87
use std::collections::HashMap;
@@ -830,6 +829,53 @@ Update the server's fingerprint with:
830829
}
831830
}
832831

832+
/// Update the value of a key in a `TOML` document, preserving the formatting and comments of the original value.
833+
///
834+
/// ie:
835+
///
836+
/// ```toml;no_run
837+
/// # Moving key = value to key = new_value
838+
/// old = "value" # Comment
839+
/// new = "new_value" # Comment
840+
/// ```
841+
fn copy_value_with_decor(old_value: Option<&toml_edit::Item>, new_value: &str) -> toml_edit::Item {
842+
match old_value {
843+
Some(toml_edit::Item::Value(toml_edit::Value::String(old_value))) => {
844+
// Creates a new `toml_edit::Value` with the same formatting as the old value.
845+
let mut new = toml_edit::Value::String(toml_edit::Formatted::new(new_value.to_string()));
846+
let decor = new.decor_mut();
847+
// Copy the comments and formatting from the old value.
848+
*decor = old_value.decor().clone();
849+
new.into()
850+
}
851+
_ => new_value.into(),
852+
}
853+
}
854+
855+
/// Set the value of a key in a `TOML` document, removing the key if the value is `None`.
856+
///
857+
/// **NOTE**: This function will preserve the formatting and comments of the original value.
858+
pub fn set_opt_value(doc: &mut toml_edit::DocumentMut, key: &str, value: Option<&str>) {
859+
let old_value = doc.get(key);
860+
if let Some(new) = value {
861+
doc[key] = copy_value_with_decor(old_value, new);
862+
} else {
863+
doc.remove(key);
864+
}
865+
}
866+
867+
/// Set the value of a key in a `TOML` table, removing the key if the value is `None`.
868+
///
869+
/// **NOTE**: This function will preserve the formatting and comments of the original value.
870+
pub fn set_table_opt_value(table: &mut toml_edit::Table, key: &str, value: Option<&str>) {
871+
let old_value = table.get(key);
872+
if let Some(new) = value {
873+
table[key] = copy_value_with_decor(old_value, new);
874+
} else {
875+
table.remove(key);
876+
}
877+
}
878+
833879
#[cfg(test)]
834880
mod tests {
835881
use super::*;

crates/cli/src/subcommands/call.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ use anyhow::{bail, Context, Error};
77
use clap::{Arg, ArgMatches};
88
use itertools::Either;
99
use serde_json::Value;
10-
use spacetimedb::Identity;
1110
use spacetimedb_lib::de::serde::deserialize_from;
1211
use spacetimedb_lib::sats::{AlgebraicType, AlgebraicTypeRef, Typespace};
13-
use spacetimedb_lib::ProductTypeElement;
12+
use spacetimedb_lib::{Identity, ProductTypeElement};
1413
use std::fmt::Write;
1514
use std::iter;
1615

crates/cli/src/subcommands/generate/mod.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use convert_case::{Case, Casing};
88
use core::mem;
99
use duct::cmd;
1010
use itertools::Itertools;
11-
use spacetimedb::host::wasmtime::{Mem, MemView, WasmPointee as _};
1211
use spacetimedb_data_structures::map::HashSet;
1312
use spacetimedb_lib::de::serde::DeserializeWrapper;
1413
use spacetimedb_lib::sats::{AlgebraicType, AlgebraicTypeRef, Typespace};
@@ -404,13 +403,13 @@ fn extract_descriptions_from_module(module: wasmtime::Module) -> anyhow::Result<
404403
message_ptr: u32,
405404
message_len: u32| {
406405
let (mem, _) = WasmCtx::mem_env(&mut caller);
407-
let slice = mem.deref_slice(message_ptr, message_len).unwrap();
406+
let slice = deref_slice(mem, message_ptr, message_len).unwrap();
408407
println!("from wasm: {}", String::from_utf8_lossy(slice));
409408
},
410409
)?;
411410
linker.func_wrap(module_name, "bytes_sink_write", WasmCtx::bytes_sink_write)?;
412411
let instance = linker.instantiate(&mut store, &module)?;
413-
let memory = Mem::extract(&instance, &mut store)?;
412+
let memory = instance.get_memory(&mut store, "memory").context("no memory export")?;
414413
store.data_mut().mem = Some(memory);
415414

416415
let mut preinits = instance
@@ -435,19 +434,26 @@ fn extract_descriptions_from_module(module: wasmtime::Module) -> anyhow::Result<
435434
}
436435

437436
struct WasmCtx {
438-
mem: Option<Mem>,
437+
mem: Option<wasmtime::Memory>,
439438
sink: Vec<u8>,
440439
}
441440

441+
fn deref_slice(mem: &[u8], offset: u32, len: u32) -> anyhow::Result<&[u8]> {
442+
anyhow::ensure!(offset != 0, "ptr is null");
443+
mem.get(offset as usize..)
444+
.and_then(|s| s.get(..len as usize))
445+
.context("pointer out of bounds")
446+
}
447+
442448
impl WasmCtx {
443-
pub fn get_mem(&self) -> Mem {
449+
pub fn get_mem(&self) -> wasmtime::Memory {
444450
self.mem.expect("Initialized memory")
445451
}
446452

447-
fn mem_env<'a>(ctx: impl Into<StoreContextMut<'a, Self>>) -> (&'a mut MemView, &'a mut Self) {
453+
fn mem_env<'a>(ctx: impl Into<StoreContextMut<'a, Self>>) -> (&'a mut [u8], &'a mut Self) {
448454
let ctx = ctx.into();
449455
let mem = ctx.data().get_mem();
450-
mem.view_and_store_mut(ctx)
456+
mem.data_and_store_mut(ctx)
451457
}
452458

453459
pub fn bytes_sink_write(
@@ -463,9 +469,9 @@ impl WasmCtx {
463469
let (mem, env) = Self::mem_env(&mut caller);
464470

465471
// Read `buffer_len`, i.e., the capacity of `buffer` pointed to by `buffer_ptr`.
466-
let buffer_len = u32::read_from(mem, buffer_len_ptr)?;
472+
let buffer_len = u32::from_le_bytes(deref_slice(mem, buffer_len_ptr, 4)?.try_into().unwrap());
467473
// Write `buffer` to `sink`.
468-
let buffer = mem.deref_slice(buffer_ptr, buffer_len)?;
474+
let buffer = deref_slice(mem, buffer_ptr, buffer_len)?;
469475
env.sink.extend(buffer);
470476

471477
Ok(0)

crates/cli/src/subcommands/list.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::Config;
44
use clap::{ArgMatches, Command};
55
use reqwest::StatusCode;
66
use serde::Deserialize;
7-
use spacetimedb::Identity;
7+
use spacetimedb_lib::Identity;
88
use tabled::{
99
settings::{object::Columns, Alignment, Modify, Style},
1010
Table, Tabled,

crates/cli/src/util.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use base64::{
55
};
66
use reqwest::RequestBuilder;
77
use serde::Deserialize;
8-
use spacetimedb::auth::identity::{IncomingClaims, SpacetimeIdentityClaims};
8+
use spacetimedb_auth::identity::{IncomingClaims, SpacetimeIdentityClaims};
99
use spacetimedb_client_api_messages::name::{DnsLookupResponse, RegisterTldResult, ReverseDNSResponse};
1010
use spacetimedb_data_structures::map::HashMap;
1111
use spacetimedb_lib::{AlgebraicType, Identity};

crates/core/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ path = "src/lib.rs" # The source file of the target.
1515
bench = false
1616

1717
[dependencies]
18+
spacetimedb-auth.workspace = true
1819
spacetimedb-data-structures.workspace = true
1920
spacetimedb-lib = { workspace = true, features = ["serde", "metrics_impls"] }
2021
spacetimedb-client-api-messages.workspace = true
@@ -95,7 +96,6 @@ tokio-util.workspace = true
9596
tokio.workspace = true
9697
tokio-stream = "0.1"
9798
toml.workspace = true
98-
toml_edit.workspace = true
9999
tracing-appender.workspace = true
100100
tracing-core.workspace = true
101101
tracing-flame.workspace = true
@@ -107,7 +107,7 @@ url.workspace = true
107107
urlencoding.workspace = true
108108
uuid.workspace = true
109109
wasmtime.workspace = true
110-
jwks = { package = "spacetimedb-jwks", version = "0.1.3" }
110+
jwks.workspace = true
111111
async_cache = "0.3.1"
112112
faststr = "0.2.23"
113113

crates/core/src/auth/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use spacetimedb_paths::cli::{PrivKeyPath, PubKeyPath};
66

77
use crate::config::CertificateAuthority;
88

9-
pub mod identity;
9+
pub use spacetimedb_auth::identity;
1010
pub mod token_validation;
1111

1212
/// JWT verification and signing keys.

crates/core/src/auth/token_validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl TokenValidator for DecodingKey {
157157

158158
let data = decode::<IncomingClaims>(token, self, &validation)?;
159159
let claims = data.claims;
160-
claims.try_into()
160+
claims.try_into().map_err(TokenValidationError::Other)
161161
}
162162
}
163163

0 commit comments

Comments
 (0)