Skip to content

Commit 6b61e0a

Browse files
committed
Move shared libraries into workspace; Spin rsky-repo out into its own crate
1 parent d13d2cc commit 6b61e0a

File tree

123 files changed

+2492
-2426
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+2492
-2426
lines changed

.idea/rsky.iml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
[workspace]
2-
members = [ "rsky-common", "rsky-crypto","rsky-feedgen", "rsky-firehose", "rsky-identity", "rsky-labeler", "rsky-lexicon", "rsky-pds", "rsky-syntax", "rsky-jetstream-subscriber"]
2+
members = [ "rsky-common", "rsky-crypto","rsky-feedgen", "rsky-firehose", "rsky-identity", "rsky-labeler", "rsky-lexicon", "rsky-pds", "rsky-syntax", "rsky-jetstream-subscriber", "rsky-repo"]
33
resolver = "2"
44

55
[workspace.dependencies]
66
cargo = { version = "0.84.0",features = ["vendored-openssl"] }
7+
serde = { version = "1.0.160", features = ["derive"] }
8+
serde_derive = "^1.0"
9+
serde_ipld_dagcbor = { version = "0.6.1" ,features = ["codec"]}
10+
lexicon_cid = { package = "cid", version = "0.10.1", features = ["serde-codec"] }
11+
libipld = "0.16.0"
12+
serde_cbor = "0.11.2"
13+
serde_bytes = "0.11.15"
14+
tokio = { version = "1.28.2",features = ["full"] }
15+
sha2 = "0.11.0-pre.3"
16+
rand = "0.8.5"
17+
rand_core = "0.6.4"
18+
secp256k1 = { version = "0.28.2", features = ["global-context", "serde", "rand", "hashes","rand-std"] }
19+
serde_json = { version = "1.0.96",features = ["preserve_order"] }
720
rsky-lexicon = {path = "rsky-lexicon", version = "0.2.3"}
821
rsky-identity = {path = "rsky-identity", version = "0.1.0"}
922
rsky-crypto = {path = "rsky-crypto", version = "0.1.1"}
1023
rsky-syntax = {path = "rsky-syntax", version = "0.1.0"}
11-
rsky-common = {path = "rsky-common", version = "0.1.0"}
24+
rsky-common = {path = "rsky-common", version = "0.1.1"}
25+
rsky-repo = {path = "rsky-repo", version = "0.0.1"}
1226

1327
[profile.release]
1428
debug = 2 # Or any level from 0 to 2

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ rsky (/ˈrɪski/) is intended to be a full implementation of [AT Protocol](https
3737
| `rsky-lexicon`: schema definition language | [README](./rsky-lexicon/README.md) | [![Crate](https://img.shields.io/crates/v/rsky-lexicon?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44)](https://crates.io/crates/rsky-lexicon) |
3838
| `rsky-syntax`: string parsers for identifiers | [README](./rsky-syntax/README.md) | [![Crate](https://img.shields.io/crates/v/rsky-syntax?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44)](https://crates.io/crates/rsky-syntax) |
3939
| `rsky-common`: shared code | [README](./rsky-common/README.md) | [![Crate](https://img.shields.io/crates/v/rsky-common?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44)](https://crates.io/crates/rsky-common) |
40+
| `rsky-repo`: data storage structure, including MST | [README](./rsky-repo/README.md) | [![Crate](https://img.shields.io/crates/v/rsky-repo?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44)](https://crates.io/crates/rsky-repo) |
4041

4142
**Rust Services:**
4243

4344
- `rsky-pds`: "Personal Data Server", hosting repo content for atproto accounts. It differs from the canonical Typescript implementation by using Postgres instead of SQLite, s3 compatible blob storage instead of on-disk, and mailgun for emailing. All to make the PDS easier to migrate between cloud hosting providers and more maintainable.
4445
- `rsky-feedgen`: Bluesky feed generator that closely follows the use cases of the Blacksky community.
4546
- `rsky-firehose`: Firehose consumer.
47+
- `rsky-jetstream-subscriber`: Firehose consumer for Jetstream.
4648
- `rsky-labeler`: Firehose consumer that labels content.
4749

4850
## About AT Protocol

rsky-common/Cargo.toml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsky-common"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
authors = ["Rudy Fraser <him@rudyfraser.com>"]
55
description = "Shared code for rsky"
66
license = "Apache-2.0"
@@ -13,6 +13,25 @@ documentation = "https://docs.rs/rsky-common"
1313

1414
[dependencies]
1515
regex = "1.8.4"
16+
serde = { version = "1.0.217", features = ["derive"] }
17+
thiserror = "2.0.11"
18+
serde_ipld_dagcbor = { workspace = true }
19+
anyhow = "1.0.79"
20+
chrono = "0.4.39"
21+
rand = {workspace = true}
22+
rand_core = { workspace = true }
23+
url = "2.5.4"
24+
serde_json = "1.0.138"
25+
tracing = "0.1.41" # @TODO: Remove anyhow in lib
26+
rsky-identity = {workspace = true}
27+
base64ct = "1.6.0"
28+
urlencoding = "2.1.3"
29+
futures = "0.3.28"
30+
libipld = {workspace = true}
31+
indexmap = { version = "1.9.3",features = ["serde-1"] }
32+
secp256k1 = {workspace = true}
33+
sha2 = {workspace = true}
34+
lexicon_cid = {workspace = true}
1635

1736
[dev-dependencies]
1837
temp-env = { version = "0.3.6"}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::common::time::SECOND;
2-
use crate::common::wait;
1+
use crate::time::SECOND;
2+
use crate::wait;
33
use futures::stream::Stream;
44
use futures::task::{Context, Poll};
55
use std::cmp;
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::common;
21
use anyhow::Result;
32
use lexicon_cid::Cid;
43
use libipld::cbor::DagCborCodec;
@@ -8,7 +7,7 @@ use libipld::raw::RawCodec;
87
use serde::Serialize;
98

109
pub fn cid_for_cbor<T: Serialize>(data: &T) -> Result<Cid> {
11-
let bytes = common::struct_to_cbor(data)?;
10+
let bytes = crate::struct_to_cbor(data)?;
1211
let cid = Cid::new_v1(
1312
u64::from(DagCborCodec),
1413
Code::Sha2_256.digest(bytes.as_slice()),

rsky-common/src/lib.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,177 @@
1+
use anyhow::Result;
2+
use base64ct::{Base64, Encoding};
3+
use chrono::offset::Utc as UtcOffset;
4+
use chrono::DateTime;
5+
use rand::{distributions::Alphanumeric, Rng};
6+
use rsky_identity::did::atproto_data::VerificationMaterial;
7+
use rsky_identity::types::DidDocument;
8+
use serde::de::DeserializeOwned;
9+
use serde::Serialize;
10+
use std::thread;
11+
use std::time::{Duration, SystemTime};
12+
use thiserror::Error;
13+
use url::Url;
14+
use urlencoding::encode;
15+
16+
pub const RFC3339_VARIANT: &str = "%Y-%m-%dT%H:%M:%S%.3fZ";
17+
18+
#[derive(Error, Debug)]
19+
pub enum BadContentTypeError {
20+
#[error("BadType: `{0}`")]
21+
BadType(String),
22+
#[error("Content-Type header is missing")]
23+
MissingType,
24+
}
25+
26+
#[derive(Debug)]
27+
pub struct GetServiceEndpointOpts {
28+
pub id: String,
29+
pub r#type: Option<String>,
30+
}
31+
32+
pub fn now() -> String {
33+
let system_time = SystemTime::now();
34+
let dt: DateTime<UtcOffset> = system_time.into();
35+
format!("{}", dt.format(RFC3339_VARIANT))
36+
}
37+
38+
pub fn wait(ms: u64) {
39+
thread::sleep(Duration::from_millis(ms))
40+
}
41+
42+
pub fn beginning_of_time() -> String {
43+
let beginning_of_time = SystemTime::UNIX_EPOCH;
44+
let dt: DateTime<UtcOffset> = beginning_of_time.into();
45+
format!("{}", dt.format(RFC3339_VARIANT))
46+
}
47+
48+
pub fn get_random_str() -> String {
49+
let token: String = rand::thread_rng()
50+
.sample_iter(&Alphanumeric)
51+
.take(32)
52+
.map(char::from)
53+
.collect();
54+
token
55+
}
56+
57+
pub fn struct_to_cbor<T: Serialize>(obj: &T) -> Result<Vec<u8>> {
58+
Ok(serde_ipld_dagcbor::to_vec(obj)?)
59+
}
60+
61+
pub fn cbor_to_struct<T: DeserializeOwned>(bytes: Vec<u8>) -> Result<T> {
62+
Ok(serde_ipld_dagcbor::from_slice::<T>(bytes.as_slice())?)
63+
}
64+
65+
pub fn json_to_b64url<T: Serialize>(obj: &T) -> Result<String> {
66+
Ok(Base64::encode_string((&serde_json::to_string(obj)?).as_ref()).replace("=", ""))
67+
}
68+
69+
pub fn encode_uri_component(input: &String) -> String {
70+
encode(input).to_string()
71+
}
72+
73+
// based on did-doc.ts
74+
pub fn get_did(doc: &DidDocument) -> String {
75+
doc.id.clone()
76+
}
77+
78+
pub fn get_handle(doc: &DidDocument) -> Option<String> {
79+
match &doc.also_known_as {
80+
None => None,
81+
Some(aka) => {
82+
let found = aka.into_iter().find(|name| name.starts_with("at://"));
83+
match found {
84+
None => None,
85+
// strip off at:// prefix
86+
Some(found) => Some(found[5..].to_string()),
87+
}
88+
}
89+
}
90+
}
91+
92+
pub fn get_verification_material(
93+
doc: &DidDocument,
94+
key_id: &String,
95+
) -> Option<VerificationMaterial> {
96+
let did = get_did(doc);
97+
let keys = &doc.verification_method;
98+
if let Some(keys) = keys {
99+
let found = keys
100+
.into_iter()
101+
.find(|key| key.id == format!("#{key_id}") || key.id == format!("{did}#{key_id}"));
102+
match found {
103+
Some(found) if found.public_key_multibase.is_some() => {
104+
let found = found.clone();
105+
Some(VerificationMaterial {
106+
r#type: found.r#type,
107+
public_key_multibase: found.public_key_multibase.unwrap(),
108+
})
109+
}
110+
_ => None,
111+
}
112+
} else {
113+
None
114+
}
115+
}
116+
117+
pub fn get_notif_endpoint(doc: DidDocument) -> Option<String> {
118+
get_service_endpoint(
119+
doc,
120+
GetServiceEndpointOpts {
121+
id: "#bsky_notif".to_string(),
122+
r#type: Some("BskyNotificationService".to_string()),
123+
},
124+
)
125+
}
126+
127+
#[tracing::instrument(skip_all)]
128+
pub fn get_service_endpoint(doc: DidDocument, opts: GetServiceEndpointOpts) -> Option<String> {
129+
tracing::info!(
130+
"@LOG: common::get_service_endpoint() doc: {:?}; opts: {:?}",
131+
doc,
132+
opts
133+
);
134+
let did = get_did(&doc);
135+
match doc.service {
136+
None => None,
137+
Some(services) => {
138+
let found = services.iter().find(|service| {
139+
service.id == opts.id || service.id == format!("{}{}", did, opts.id)
140+
});
141+
match found {
142+
None => None,
143+
Some(found) => match opts.r#type {
144+
None => None,
145+
Some(opts_type) if found.r#type == opts_type => {
146+
validate_url(&found.service_endpoint)
147+
}
148+
_ => None,
149+
},
150+
}
151+
}
152+
}
153+
}
154+
155+
// Check protocol and hostname to prevent potential SSRF
156+
pub fn validate_url(url_str: &String) -> Option<String> {
157+
match Url::parse(url_str) {
158+
Err(_) => None,
159+
Ok(url) => {
160+
return if !vec!["http", "https"].contains(&url.scheme()) {
161+
None
162+
} else if url.host().is_none() {
163+
None
164+
} else {
165+
Some(url_str.clone())
166+
}
167+
}
168+
}
169+
}
170+
171+
pub mod r#async;
1172
pub mod env;
2173
pub mod explicit_slurs;
174+
pub mod ipld;
175+
pub mod sign;
176+
pub mod tid;
177+
pub mod time;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub fn dedash(str: String) -> String {
2828
}
2929

3030
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
31-
pub struct TID(pub(crate) String);
31+
pub struct TID(pub String);
3232

3333
impl TID {
3434
pub fn new(str: String) -> Result<Self> {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::common::RFC3339_VARIANT;
1+
use crate::RFC3339_VARIANT;
22
use anyhow::Result;
33
use chrono::offset::Utc as UtcOffset;
44
use chrono::{DateTime, NaiveDateTime, Utc};

0 commit comments

Comments
 (0)