From c9ccbbb77cb8fcb691880e9b564ba0d120b8bdc2 Mon Sep 17 00:00:00 2001 From: nazeh Date: Thu, 18 Jul 2024 13:18:01 +0300 Subject: [PATCH] feat(homeserver): add Users table --- Cargo.lock | 70 +++++++++++++++++++ pubky-homeserver/Cargo.toml | 2 + pubky-homeserver/src/database.rs | 27 +++++-- pubky-homeserver/src/database/migrations.rs | 11 +++ pubky-homeserver/src/database/tables.rs | 1 + pubky-homeserver/src/database/tables/users.rs | 59 ++++++++++++++++ pubky-homeserver/src/error.rs | 33 +++++---- pubky-homeserver/src/routes/auth.rs | 20 +++++- 8 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 pubky-homeserver/src/database/migrations.rs create mode 100644 pubky-homeserver/src/database/tables.rs create mode 100644 pubky-homeserver/src/database/tables/users.rs diff --git a/Cargo.lock b/Cargo.lock index 38919d1..d8be522 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -219,6 +228,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "const-oid" version = "0.9.6" @@ -264,6 +279,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -406,6 +427,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -567,6 +594,29 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heed" version = "0.20.3" @@ -1043,6 +1093,18 @@ dependencies = [ "spki", ] +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "heapless", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1095,7 +1157,9 @@ dependencies = [ "dirs-next", "heed", "pkarr", + "postcard", "pubky-common", + "serde", "tokio", "tower-http", "tracing", @@ -1472,6 +1536,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "subtle" version = "2.6.1" diff --git a/pubky-homeserver/Cargo.toml b/pubky-homeserver/Cargo.toml index 08d3b80..d7bde5e 100644 --- a/pubky-homeserver/Cargo.toml +++ b/pubky-homeserver/Cargo.toml @@ -10,7 +10,9 @@ bytes = "1.6.1" dirs-next = "2.0.0" heed = "0.20.3" pkarr = { version = "2.1.0", features = ["async"] } +postcard = { version = "1.0.8", features = ["alloc"] } pubky-common = { version = "0.1.0", path = "../pubky-common" } +serde = { version = "1.0.204", features = ["derive"] } tokio = { version = "1.37.0", features = ["full"] } tower-http = { version = "0.5.2", features = ["cors", "trace"] } tracing = "0.1.40" diff --git a/pubky-homeserver/src/database.rs b/pubky-homeserver/src/database.rs index 6332b0f..4e4a57e 100644 --- a/pubky-homeserver/src/database.rs +++ b/pubky-homeserver/src/database.rs @@ -1,19 +1,38 @@ use std::fs; use std::path::Path; -use heed::{Env, EnvOpenOptions}; +use heed::{types::Str, Database, Env, EnvOpenOptions, RwTxn}; + +mod migrations; +pub mod tables; + +use migrations::TABLES_COUNT; #[derive(Debug, Clone)] pub struct DB { - env: Env, + pub(crate) env: Env, } impl DB { pub fn open(storage: &Path) -> anyhow::Result { fs::create_dir_all(storage).unwrap(); - let env = unsafe { EnvOpenOptions::new().open(storage) }?; + let env = unsafe { EnvOpenOptions::new().max_dbs(TABLES_COUNT).open(storage) }?; - Ok(DB { env }) + let db = DB { env }; + + db.run_migrations(); + + Ok(db) + } + + fn run_migrations(&self) -> anyhow::Result<()> { + let mut wtxn = self.env.write_txn()?; + + migrations::create_users_table(&self.env, &mut wtxn); + + wtxn.commit()?; + + Ok(()) } } diff --git a/pubky-homeserver/src/database/migrations.rs b/pubky-homeserver/src/database/migrations.rs new file mode 100644 index 0000000..428a2f4 --- /dev/null +++ b/pubky-homeserver/src/database/migrations.rs @@ -0,0 +1,11 @@ +use heed::{types::Str, Database, Env, RwTxn}; + +use super::tables; + +pub const TABLES_COUNT: u32 = 1; + +pub fn create_users_table(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> { + let _: tables::users::UsersTable = env.create_database(wtxn, None)?; + + Ok(()) +} diff --git a/pubky-homeserver/src/database/tables.rs b/pubky-homeserver/src/database/tables.rs new file mode 100644 index 0000000..913bd46 --- /dev/null +++ b/pubky-homeserver/src/database/tables.rs @@ -0,0 +1 @@ +pub mod users; diff --git a/pubky-homeserver/src/database/tables/users.rs b/pubky-homeserver/src/database/tables/users.rs new file mode 100644 index 0000000..e785245 --- /dev/null +++ b/pubky-homeserver/src/database/tables/users.rs @@ -0,0 +1,59 @@ +use std::{borrow::Cow, time::SystemTime}; + +use postcard::{from_bytes, to_allocvec}; +use pubky_common::timestamp::Timestamp; +use serde::{Deserialize, Serialize}; + +use heed::{types::Str, BoxedError, BytesDecode, BytesEncode, Database}; +use pkarr::PublicKey; + +extern crate alloc; +use alloc::vec::Vec; + +/// PublicKey => User. +pub type UsersTable = Database; + +pub const USERS_TABLE: &str = "users"; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct User { + pub created_at: u64, +} + +impl<'a> BytesEncode<'a> for User { + type EItem = Self; + + fn bytes_encode(user: &Self::EItem) -> Result, BoxedError> { + let vec = to_allocvec(user).unwrap(); + + Ok(Cow::Owned(vec)) + } +} + +impl<'a> BytesDecode<'a> for User { + type DItem = Self; + + fn bytes_decode(bytes: &'a [u8]) -> Result { + let user: User = from_bytes(bytes).unwrap(); + + Ok(user) + } +} + +pub struct PublicKeyCodec {} + +impl<'a> BytesEncode<'a> for PublicKeyCodec { + type EItem = PublicKey; + + fn bytes_encode(pubky: &Self::EItem) -> Result, BoxedError> { + Ok(Cow::Borrowed(pubky.as_bytes())) + } +} + +impl<'a> BytesDecode<'a> for PublicKeyCodec { + type DItem = PublicKey; + + fn bytes_decode(bytes: &'a [u8]) -> Result { + Ok(PublicKey::try_from(bytes)?) + } +} diff --git a/pubky-homeserver/src/error.rs b/pubky-homeserver/src/error.rs index 2f92b7d..46d37d6 100644 --- a/pubky-homeserver/src/error.rs +++ b/pubky-homeserver/src/error.rs @@ -6,6 +6,7 @@ use axum::{ response::IntoResponse, }; use pubky_common::auth::AuthnSignatureError; +use tracing::debug; pub type Result = core::result::Result; @@ -52,39 +53,47 @@ impl IntoResponse for Error { } impl From for Error { - fn from(value: QueryRejection) -> Self { - Self::new(StatusCode::BAD_REQUEST, Some(value)) + fn from(error: QueryRejection) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) } } impl From for Error { - fn from(value: ExtensionRejection) -> Self { - Self::new(StatusCode::BAD_REQUEST, Some(value)) + fn from(error: ExtensionRejection) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) } } impl From for Error { - fn from(value: PathRejection) -> Self { - Self::new(StatusCode::BAD_REQUEST, Some(value)) + fn from(error: PathRejection) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) } } impl From for Error { - fn from(value: std::io::Error) -> Self { - Self::new(StatusCode::INTERNAL_SERVER_ERROR, Some(value)) + fn from(error: std::io::Error) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, Some(error)) } } // === Pubky specific errors === impl From for Error { - fn from(value: AuthnSignatureError) -> Self { - Self::new(StatusCode::BAD_REQUEST, Some(value)) + fn from(error: AuthnSignatureError) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) } } impl From for Error { - fn from(value: pkarr::Error) -> Self { - Self::new(StatusCode::BAD_REQUEST, Some(value)) + fn from(error: pkarr::Error) -> Self { + Self::new(StatusCode::BAD_REQUEST, Some(error)) + } +} + +impl From for Error { + fn from(error: heed::Error) -> Self { + debug!(?error); + + Self::with_status(StatusCode::INTERNAL_SERVER_ERROR) } } diff --git a/pubky-homeserver/src/routes/auth.rs b/pubky-homeserver/src/routes/auth.rs index 0fc333e..fdcb81b 100644 --- a/pubky-homeserver/src/routes/auth.rs +++ b/pubky-homeserver/src/routes/auth.rs @@ -1,7 +1,14 @@ use axum::{extract::State, response::IntoResponse}; use bytes::Bytes; -use crate::{error::Result, extractors::Pubky, server::AppState}; +use pubky_common::timestamp::Timestamp; + +use crate::{ + database::tables::users::{User, UsersTable, USERS_TABLE}, + error::Result, + extractors::Pubky, + server::AppState, +}; pub async fn signup( State(state): State, @@ -10,7 +17,16 @@ pub async fn signup( ) -> Result { state.verifier.verify(&body, pubky.public_key())?; - // TODO: store account in database. + let mut wtxn = state.db.env.write_txn()?; + let users: UsersTable = state.db.env.create_database(&mut wtxn, Some(USERS_TABLE))?; + + users.put( + &mut wtxn, + pubky.public_key(), + &User { + created_at: Timestamp::now().into_inner(), + }, + )?; Ok(()) }