···11+[package]
22+name = "pocket"
33+version = "0.1.0"
44+edition = "2024"
55+66+[dependencies]
77+atrium-crypto = "0.1.2"
88+clap = { version = "4.5.41", features = ["derive"] }
99+jwt-compact = { git = "https://github.com/fatfingers23/jwt-compact.git", features = ["es256k"] }
1010+log = "0.4.27"
1111+poem = { version = "3.1.12", features = ["acme", "static-files"] }
1212+poem-openapi = { version = "5.1.16", features = ["scalar"] }
1313+reqwest = { version = "0.12.22", features = ["json"] }
1414+rusqlite = "0.37.0"
1515+serde = { version = "1.0.219", features = ["derive"] }
1616+serde_json = { version = "1.0.141" }
1717+thiserror = "2.0.16"
1818+tokio = { version = "1.47.0", features = ["full"] }
1919+tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
+17
pocket/api-description.md
···11+_A pocket dimension to stash a bit of non-public user data._
22+33+44+# Pocket: user preference storage
55+66+This API leverages atproto service proxying to offer a bit of per-user per-app non-public data storage.
77+Perfect for things like application preferences that might be better left out of the public PDS data.
88+99+The intent is to use oauth scopes to isolate storage on a per-application basis, and to allow easy data migration from a community hosted instance to your own if you end up needing that.
1010+1111+1212+### Current status
1313+1414+> [!important]
1515+> Pocket is currently in a **v0, pre-release state**. There is one production instance and you can use it! Expect short downtimes for restarts as development progresses and occaisional data loss until it's stable.
1616+1717+ATProto might end up adding a similar feature to [PDSs](https://atproto.com/guides/glossary#pds-personal-data-server). If/when that happens, you should use it instead of this!
+5
pocket/src/lib.rs
···11+mod server;
22+mod token;
33+44+pub use server::serve;
55+pub use token::TokenVerifier;
···11+[package]
22+name = "reflector"
33+version = "0.1.0"
44+edition = "2024"
55+66+[dependencies]
77+clap = { version = "4.5.47", features = ["derive"] }
88+log = "0.4.28"
99+poem = "3.1.12"
1010+serde = { version = "1.0.219", features = ["derive"] }
1111+tokio = "1.47.1"
1212+tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
+9
reflector/readme.md
···11+# reflector
22+33+a tiny did:web service server that maps subdomains to a single service endpoint
44+55+receiving requests from multiple subdomains is left as a problem for the reverse proxy to solve, since acme wildcard certificates (ie. letsencrypt) require the most complicated and involved challenge type (DNS).
66+77+caddy [has good support for](https://caddyserver.com/docs/caddyfile/patterns#wildcard-certificates) configuring the wildcard DNS challenge with various DNS providers, and also supports [on-demand](https://caddyserver.com/docs/automatic-https#using-on-demand-tls) provisioning via the simpler methods.
88+99+if you only need a small fixed number of subdomains, you can also use certbot or otherwise individually configure them in your reverse proxy.
+83
reflector/src/main.rs
···11+use clap::Parser;
22+use poem::{
33+ EndpointExt, Route, Server, get, handler,
44+ listener::TcpListener,
55+ middleware::{AddData, Tracing},
66+ web::{Data, Json, TypedHeader, headers::Host},
77+};
88+use serde::Serialize;
99+1010+#[handler]
1111+fn hello() -> String {
1212+ "ɹoʇɔǝʅⅎǝɹ".to_string()
1313+}
1414+1515+#[derive(Debug, Serialize)]
1616+struct DidDoc {
1717+ id: String,
1818+ service: [DidService; 1],
1919+}
2020+2121+#[derive(Debug, Clone, Serialize)]
2222+struct DidService {
2323+ id: String,
2424+ r#type: String,
2525+ service_endpoint: String,
2626+}
2727+2828+#[handler]
2929+fn did_doc(TypedHeader(host): TypedHeader<Host>, service: Data<&DidService>) -> Json<DidDoc> {
3030+ Json(DidDoc {
3131+ id: format!("did:web:{}", host.hostname()),
3232+ service: [service.clone()],
3333+ })
3434+}
3535+3636+/// Slingshot record edge cache
3737+#[derive(Parser, Debug, Clone)]
3838+#[command(version, about, long_about = None)]
3939+struct Args {
4040+ /// The DID document service ID to serve
4141+ ///
4242+ /// must start with a '#', like `#bsky_appview'
4343+ #[arg(long)]
4444+ id: String,
4545+ /// Service type
4646+ ///
4747+ /// Not sure exactly what its requirements are. 'BlueskyAppview' for example
4848+ #[arg(long)]
4949+ r#type: String,
5050+ /// The HTTPS endpoint for the service
5151+ #[arg(long)]
5252+ service_endpoint: String,
5353+}
5454+5555+impl From<Args> for DidService {
5656+ fn from(a: Args) -> Self {
5757+ Self {
5858+ id: a.id,
5959+ r#type: a.r#type,
6060+ service_endpoint: a.service_endpoint,
6161+ }
6262+ }
6363+}
6464+6565+#[tokio::main(flavor = "current_thread")]
6666+async fn main() {
6767+ tracing_subscriber::fmt::init();
6868+ log::info!("ɹoʇɔǝʅⅎǝɹ");
6969+7070+ let args = Args::parse();
7171+ let service: DidService = args.into();
7272+7373+ Server::new(TcpListener::bind("0.0.0.0:3001"))
7474+ .run(
7575+ Route::new()
7676+ .at("/", get(hello))
7777+ .at("/.well-known/did.json", get(did_doc))
7878+ .with(AddData::new(service))
7979+ .with(Tracing),
8080+ )
8181+ .await
8282+ .unwrap()
8383+}