aboutsummaryrefslogtreecommitdiffstats
path: root/adenosine-pds/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'adenosine-pds/src/lib.rs')
-rw-r--r--adenosine-pds/src/lib.rs206
1 files changed, 140 insertions, 66 deletions
diff --git a/adenosine-pds/src/lib.rs b/adenosine-pds/src/lib.rs
index 3389281..468cc6e 100644
--- a/adenosine-pds/src/lib.rs
+++ b/adenosine-pds/src/lib.rs
@@ -17,7 +17,7 @@ mod car;
mod crypto;
mod db;
mod did;
-mod models;
+pub mod models;
pub mod mst;
mod repo;
mod ucan_p256;
@@ -26,6 +26,7 @@ mod web;
pub use crypto::{KeyPair, PubKey};
pub use db::AtpDatabase;
+pub use did::DidDocMeta;
pub use models::*;
pub use repo::{Mutation, RepoCommit, RepoStore};
pub use ucan_p256::P256KeyMaterial;
@@ -44,6 +45,20 @@ pub struct AtpServiceConfig {
pub listen_host_port: String,
pub public_url: String,
pub registration_domain: Option<String>,
+ pub invite_code: Option<String>,
+ pub homepage_did: Option<Did>,
+}
+
+impl Default for AtpServiceConfig {
+ fn default() -> Self {
+ AtpServiceConfig {
+ listen_host_port: "localhost:3030".to_string(),
+ public_url: "http://localhost".to_string(),
+ registration_domain: None,
+ invite_code: None,
+ homepage_did: None,
+ }
+ }
}
#[derive(Debug)]
@@ -141,19 +156,23 @@ impl AtpService {
rouille::start_server(config.listen_host_port, move |request| {
rouille::log_custom(request, log_ok, log_err, || {
router!(request,
+ // ============= Web Interface
(GET) ["/"] => {
- let view = GenericHomeView { domain: "domain.todo".to_string() };
- Response::html(view.render().unwrap())
+ let host = request.header("Host").unwrap_or("localhost");
+ if Some(host.to_string()) == config.registration_domain {
+ let view = GenericHomeView { domain: host.to_string() };
+ Response::html(view.render().unwrap())
+ } else {
+ web_wrap(home_profile_handler(&srv, request))
+ }
},
(GET) ["/about"] => {
- let view = AboutView { domain: "domain.todo".to_string() };
+ let host = request.header("Host").unwrap_or("localhost");
+ let view = AboutView { domain: host.to_string() };
Response::html(view.render().unwrap())
},
- (GET) ["/robots.txt"] => {
- Response::text(include_str!("../templates/robots.txt"))
- },
- (GET) ["/u/{did}", did: Did] => {
- web_wrap(profile_handler(&srv, &did, request))
+ (GET) ["/u/{handle}", handle: String] => {
+ web_wrap(profile_handler(&srv, &handle, request))
},
(GET) ["/u/{did}/{collection}/{tid}", did: Did, collection: Nsid, tid: Tid] => {
web_wrap(post_handler(&srv, &did, &collection, &tid, request))
@@ -167,6 +186,7 @@ impl AtpService {
(GET) ["/at/{did}/{collection}/{tid}", did: Did, collection: Nsid, tid: Tid] => {
web_wrap(record_handler(&srv, &did, &collection, &tid, request))
},
+ // ============ Static Files (compiled in to executable)
(GET) ["/static/adenosine.css"] => {
Response::from_data("text/css", include_str!("../templates/adenosine.css"))
},
@@ -176,6 +196,10 @@ impl AtpService {
(GET) ["/static/logo_128.png"] => {
Response::from_data("image/png", include_bytes!("../templates/logo_128.png").to_vec())
},
+ (GET) ["/robots.txt"] => {
+ Response::text(include_str!("../templates/robots.txt"))
+ },
+ // ============ XRPC AT Protocol
(POST) ["/xrpc/{endpoint}", endpoint: String] => {
xrpc_wrap(xrpc_post_handler(&srv, &endpoint, request))
},
@@ -289,7 +313,14 @@ fn xrpc_get_handler(
) -> Result<serde_json::Value> {
match method {
"com.atproto.server.getAccountsConfig" => {
- Ok(json!({"availableUserDomains": ["test"], "inviteCodeRequired": false}))
+ let srv = srv.lock().expect("service mutex");
+ let mut avail_domains = vec![];
+ if let Some(domain) = &srv.config.registration_domain {
+ avail_domains.push(domain)
+ }
+ Ok(
+ json!({"availableUserDomains": avail_domains, "inviteCodeRequired": srv.config.invite_code.is_some()}),
+ )
}
"com.atproto.repo.getRecord" => {
let did = Did::from_str(&xrpc_required_param(request, "user")?)?;
@@ -370,6 +401,70 @@ fn xrpc_get_repo_handler(srv: &Mutex<AtpService>, request: &Request) -> Result<V
Ok(srv.repo.export_car(&commit_cid, None)?)
}
+pub fn create_account(
+ srv: &mut AtpService,
+ req: &AccountRequest,
+ create_did_plc: bool,
+) -> Result<AtpSession> {
+ // check if account already exists (fast path, also confirmed by database schema)
+ if srv.atp_db.account_exists(&req.handle, &req.email)? {
+ Err(XrpcError::BadRequest(
+ "handle or email already exists".to_string(),
+ ))?;
+ };
+
+ debug!("trying to create new account: {}", &req.handle);
+
+ let (did, did_doc) = if create_did_plc {
+ // generate DID
+ let create_op = did::CreateOp::new(
+ req.handle.clone(),
+ srv.config.public_url.clone(),
+ &srv.pds_keypair,
+ req.recoveryKey.clone(),
+ );
+ create_op.verify_self()?;
+ let did = create_op.did_plc();
+ let did_doc = create_op.did_doc();
+ (did, did_doc)
+ } else {
+ let did = Did::from_str(&format!("did:web:{}", req.handle))?;
+ let signing_key = srv.pds_keypair.pubkey().to_did_key();
+ let recovery_key = req.recoveryKey.clone().unwrap_or(signing_key.clone());
+ let meta = DidDocMeta {
+ did: did.clone(),
+ user_url: format!("https://{}", req.handle),
+ service_url: srv.config.public_url.clone(),
+ recovery_didkey: recovery_key,
+ signing_didkey: signing_key,
+ };
+ (did, meta.did_doc())
+ };
+
+ // register in ATP DB and generate DID doc
+ let recovery_key = req
+ .recoveryKey
+ .clone()
+ .unwrap_or(srv.pds_keypair.pubkey().to_did_key());
+ srv.atp_db
+ .create_account(&did, &req.handle, &req.password, &req.email, &recovery_key)?;
+ srv.atp_db.put_did_doc(&did, &did_doc)?;
+
+ // insert empty MST repository
+ let root_cid = {
+ let empty_map_cid = srv.repo.mst_from_map(&Default::default())?;
+ let meta_cid = srv.repo.write_metadata(&did)?;
+ srv.repo.write_root(meta_cid, None, empty_map_cid)?
+ };
+ let _commit_cid = srv.repo.write_commit(&did, root_cid, "XXX-dummy-sig")?;
+
+ let keypair = srv.pds_keypair.clone();
+ let sess = srv
+ .atp_db
+ .create_session(&req.handle, &req.password, &keypair)?;
+ Ok(sess)
+}
+
fn xrpc_post_handler(
srv: &Mutex<AtpService>,
method: &str,
@@ -381,53 +476,8 @@ fn xrpc_post_handler(
let req: AccountRequest = rouille::input::json_input(request)
.map_err(|e| XrpcError::BadRequest(format!("failed to parse JSON body: {}", e)))?;
// TODO: validate handle, email, recoverykey
-
- // check if account already exists (fast path, also confirmed by database schema)
let mut srv = srv.lock().unwrap();
- if srv.atp_db.account_exists(&req.handle, &req.email)? {
- Err(XrpcError::BadRequest(
- "handle or email already exists".to_string(),
- ))?;
- };
-
- debug!("trying to create new account: {}", &req.handle);
-
- // generate DID
- let create_op = did::CreateOp::new(
- req.handle.clone(),
- srv.config.public_url.clone(),
- &srv.pds_keypair,
- req.recoveryKey.clone(),
- );
- create_op.verify_self()?;
- let did = create_op.did_plc();
- let did_doc = create_op.did_doc();
-
- // register in ATP DB and generate DID doc
- let recovery_key = req
- .recoveryKey
- .unwrap_or(srv.pds_keypair.pubkey().to_did_key());
- srv.atp_db.create_account(
- &did,
- &req.handle,
- &req.password,
- &req.email,
- &recovery_key,
- )?;
- srv.atp_db.put_did_doc(&did, &did_doc)?;
-
- // insert empty MST repository
- let root_cid = {
- let empty_map_cid = srv.repo.mst_from_map(&Default::default())?;
- let meta_cid = srv.repo.write_metadata(&did)?;
- srv.repo.write_root(meta_cid, None, empty_map_cid)?
- };
- let _commit_cid = srv.repo.write_commit(&did, root_cid, "XXX-dummy-sig")?;
-
- let keypair = srv.pds_keypair.clone();
- let sess = srv
- .atp_db
- .create_session(&req.handle, &req.password, &keypair)?;
+ let sess = create_account(&mut srv, &req, true)?;
Ok(json!(sess))
}
"com.atproto.session.create" => {
@@ -562,7 +612,27 @@ fn xrpc_post_handler(
}
}
-fn profile_handler(srv: &Mutex<AtpService>, did: &str, _request: &Request) -> Result<String> {
+fn home_profile_handler(srv: &Mutex<AtpService>, request: &Request) -> Result<String> {
+ let host = request.header("Host").unwrap_or("localhost");
+ // XXX
+ let did = Did::from_str(host)?;
+ let mut _srv = srv.lock().expect("service mutex");
+
+ // TODO: get profile (bsky helper)
+ // TODO: get feed (bsky helper)
+
+ Ok(ProfileView {
+ domain: host.to_string(),
+ did: did,
+ profile: json!({}),
+ feed: vec![],
+ }
+ .render()?)
+}
+
+// TODO: did, collection, tid have already been parsed by this point
+fn profile_handler(srv: &Mutex<AtpService>, did: &str, request: &Request) -> Result<String> {
+ let host = request.header("Host").unwrap_or("localhost");
let did = Did::from_str(did)?;
let mut _srv = srv.lock().expect("service mutex");
@@ -570,7 +640,7 @@ fn profile_handler(srv: &Mutex<AtpService>, did: &str, _request: &Request) -> Re
// TODO: get feed (bsky helper)
Ok(ProfileView {
- domain: "domain.todo".to_string(),
+ domain: host.to_string(),
did: did,
profile: json!({}),
feed: vec![],
@@ -583,8 +653,9 @@ fn post_handler(
did: &str,
collection: &str,
tid: &str,
- _request: &Request,
+ request: &Request,
) -> Result<String> {
+ let host = request.header("Host").unwrap_or("localhost");
let did = Did::from_str(did)?;
let collection = Nsid::from_str(collection)?;
let rkey = Tid::from_str(tid)?;
@@ -601,7 +672,7 @@ fn post_handler(
};
Ok(PostView {
- domain: "domain.todo".to_string(),
+ domain: host.to_string(),
did: did,
collection: collection,
tid: rkey,
@@ -611,7 +682,8 @@ fn post_handler(
.render()?)
}
-fn repo_handler(srv: &Mutex<AtpService>, did: &str, _request: &Request) -> Result<String> {
+fn repo_handler(srv: &Mutex<AtpService>, did: &str, request: &Request) -> Result<String> {
+ let host = request.header("Host").unwrap_or("localhost");
let did = Did::from_str(did)?;
let mut srv = srv.lock().expect("service mutex");
@@ -628,7 +700,7 @@ fn repo_handler(srv: &Mutex<AtpService>, did: &str, _request: &Request) -> Resul
};
Ok(RepoView {
- domain: "domain.todo".to_string(),
+ domain: host.to_string(),
did: did,
commit: commit,
describe: desc,
@@ -640,8 +712,9 @@ fn collection_handler(
srv: &Mutex<AtpService>,
did: &str,
collection: &str,
- _request: &Request,
+ request: &Request,
) -> Result<String> {
+ let host = request.header("Host").unwrap_or("localhost");
let did = Did::from_str(did)?;
let collection = Nsid::from_str(collection)?;
@@ -665,7 +738,7 @@ fn collection_handler(
}
Ok(CollectionView {
- domain: "domain.todo".to_string(),
+ domain: host.to_string(),
did: did,
collection: collection,
records: record_list,
@@ -678,8 +751,9 @@ fn record_handler(
did: &str,
collection: &str,
tid: &str,
- _request: &Request,
+ request: &Request,
) -> Result<String> {
+ let host = request.header("Host").unwrap_or("localhost");
let did = Did::from_str(did)?;
let collection = Nsid::from_str(collection)?;
let rkey = Tid::from_str(tid)?;
@@ -695,7 +769,7 @@ fn record_handler(
Err(e) => Err(e)?,
};
Ok(RecordView {
- domain: "domain.todo".to_string(),
+ domain: host.to_string(),
did,
collection,
tid: rkey,