diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8304858 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +.pds-data/ +target/ +tmp/ + diff --git a/Cargo.lock b/Cargo.lock index ac4e5ac..71ddb9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-lock" version = "3.4.0" @@ -283,6 +295,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -667,11 +688,16 @@ dependencies = [ name = "entryway" version = "0.1.0" dependencies = [ + "argon2", + "async-trait", "atproto", "http 1.3.1", "router", "serde", "serde_json", + "sqlx", + "thiserror 2.0.12", + "time", "tokio", "tracing", "tracing-subscriber", @@ -1589,6 +1615,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" diff --git a/entryway/migrations/01_initial_schema.sql b/entryway/migrations/01_initial_schema.sql new file mode 100644 index 0000000..a6b1ea8 --- /dev/null +++ b/entryway/migrations/01_initial_schema.sql @@ -0,0 +1,23 @@ +-- PDS Entryway Account Management Schema +-- Minimal schema for account creation and authentication + +-- Actor table - stores public identity information +CREATE TABLE actor ( + did VARCHAR PRIMARY KEY, + handle VARCHAR, + created_at VARCHAR NOT NULL +); + +-- Case-insensitive unique index on handle +CREATE UNIQUE INDEX actor_handle_lower_idx ON actor (LOWER(handle)); + +-- Account table - stores private authentication data +CREATE TABLE account ( + did VARCHAR PRIMARY KEY, + email VARCHAR NOT NULL, + password_scrypt VARCHAR NOT NULL, + email_confirmed_at VARCHAR +); + +-- Case-insensitive unique index on email +CREATE UNIQUE INDEX account_email_lower_idx ON account (LOWER(email)); diff --git a/router/src/lib.rs b/router/src/lib.rs index bddad49..788556b 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -18,13 +18,21 @@ impl Default for Router { Self::new() } } -impl Router { +impl Router +where + S: Clone + Send + Sync + 'static, +{ pub fn new() -> Self { let router = AxumRouter::new(); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127,0,0,1)), 6702); Router { router, addr } } + pub fn with_state(mut self, state: S) -> Router { + self.router = self.with_state(S); + self + } + pub fn add_endpoint(mut self, endpoint: E) -> Self { self.router = endpoint.add_to_router(self.router); self diff --git a/router/src/xrpc.rs b/router/src/xrpc.rs index 0ed8c36..ff62711 100644 --- a/router/src/xrpc.rs +++ b/router/src/xrpc.rs @@ -52,8 +52,11 @@ pub fn response(code: StatusCode, message: &str) -> Response { error(code, "", message) } -pub struct QueryInput { +pub struct QueryInput +where S: Clone + Send + Sync + 'static, +{ pub parameters: HashMap, + pub state: S, } impl FromRequestParts for QueryInput where @@ -61,23 +64,26 @@ where { type Rejection = Response; - async fn from_request_parts(parts: &mut Parts, _state: &S) + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let query_params: Result>, QueryRejection> = Query::try_from_uri(&parts.uri); match query_params { - Ok(p) => Ok(QueryInput { parameters: p.0 }), + Ok(p) => Ok(QueryInput { parameters: p.0, state }), Err(e) => Err(error(StatusCode::BAD_REQUEST, "Bad Parameters", &e.body_text())), } } } #[derive(Debug)] -pub struct ProcedureInput { +pub struct ProcedureInput +where S: Clone + Send + Sync + 'static, +{ pub parameters: HashMap, pub input: J, + pub state: S, } -impl FromRequest for ProcedureInput +impl FromRequest for ProcedureInput where J: for<'de> serde::Deserialize<'de> + Send + 'static, Bytes: FromRequest, @@ -95,7 +101,7 @@ where .map(|Json(v)| v) .map_err(|e| error(StatusCode::BAD_REQUEST, "Bad Parameters", &e.body_text()))?; - Ok(ProcedureInput { parameters, input }) + Ok(ProcedureInput { parameters, input, state }) } } @@ -125,14 +131,14 @@ where } impl XrpcEndpoint { - pub fn new_query(nsid: Nsid, query: Q) -> Self + pub fn new_query(nsid: Nsid, query: Q) -> Self where Q: XrpcHandler + Clone { XrpcEndpoint { path: Path::Nsid(nsid), - resolver: get(async move | mut parts: Parts | -> Response { - match QueryInput::from_request_parts(&mut parts, &()).await { + resolver: get(async move | mut parts: Parts, state: &S | -> Response { + match QueryInput::from_request_parts(&mut parts, state).await { Ok(qi) => query.call(qi).await, Err(e) => e } @@ -140,15 +146,15 @@ impl XrpcEndpoint { } } - pub fn new_procedure(nsid: Nsid, procedure: P) -> Self + pub fn new_procedure(nsid: Nsid, procedure: P) -> Self where - P: XrpcHandler> + Clone, + P: XrpcHandler> + Clone, J: for<'de> serde::Deserialize<'de> + Send + 'static, { XrpcEndpoint { path: Path::Nsid(nsid), - resolver: post(async move | req: Request | -> Response { - match ProcedureInput::::from_request(req, &()).await { + resolver: post(async move | req: Request, state: &S | -> Response { + match ProcedureInput::::from_request(req, &state).await { Ok(pi) => procedure.call(pi).await, Err(e) => e }