feat(homeserver): check session exists

This commit is contained in:
nazeh
2024-07-20 13:10:54 +03:00
parent c9ccbbb77c
commit 4f87c7d444
12 changed files with 328 additions and 13 deletions

153
Cargo.lock generated
View File

@@ -125,6 +125,29 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-extra"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
dependencies = [
"axum",
"axum-core",
"bytes",
"futures-util",
"headers",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"serde",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "backtrace"
version = "0.3.73"
@@ -142,9 +165,15 @@ dependencies = [
[[package]]
name = "base32"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1ce0365f4d5fb6646220bb52fe547afd51796d90f914d4063cb0b032ebee088"
checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076"
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
@@ -246,6 +275,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cpufeatures"
version = "0.2.12"
@@ -347,6 +387,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -603,6 +652,30 @@ dependencies = [
"byteorder",
]
[[package]]
name = "headers"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http",
]
[[package]]
name = "heapless"
version = "0.7.17"
@@ -913,6 +986,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num_cpus"
version = "1.16.0"
@@ -1105,6 +1184,12 @@ dependencies = [
"serde",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1153,6 +1238,8 @@ version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"axum-extra",
"base32",
"bytes",
"dirs-next",
"heed",
@@ -1161,6 +1248,7 @@ dependencies = [
"pubky-common",
"serde",
"tokio",
"tower-cookies",
"tower-http",
"tracing",
"tracing-subscriber",
@@ -1433,6 +1521,17 @@ dependencies = [
"serde",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
@@ -1610,6 +1709,37 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@@ -1671,6 +1801,23 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-cookies"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fd0118512cf0b3768f7fcccf0bef1ae41d68f2b45edc1e77432b36c97c56c6d"
dependencies = [
"async-trait",
"axum-core",
"cookie",
"futures-util",
"http",
"parking_lot",
"pin-project-lite",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.5.2"
@@ -1801,7 +1948,7 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea"
dependencies = [
"base64",
"base64 0.22.1",
"flate2",
"log",
"once_cell",

View File

@@ -12,3 +12,12 @@ pub fn random_hash() -> Hash {
let mut rng = rand::thread_rng();
Hash::from_bytes(rng.gen())
}
pub fn random_bytes<const N: usize>() -> [u8; N] {
let mut rng = rand::thread_rng();
let mut arr = [0u8; N];
for i in 0..N {
arr[i] = rng.gen();
}
arr
}

View File

@@ -6,6 +6,8 @@ edition = "2021"
[dependencies]
anyhow = "1.0.82"
axum = "0.7.5"
axum-extra = { version = "0.9.3", features = ["typed-header"] }
base32 = "0.5.1"
bytes = "1.6.1"
dirs-next = "2.0.0"
heed = "0.20.3"
@@ -14,6 +16,7 @@ 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-cookies = "0.10.0"
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }

View File

@@ -30,6 +30,7 @@ impl DB {
let mut wtxn = self.env.write_txn()?;
migrations::create_users_table(&self.env, &mut wtxn);
migrations::create_sessions_table(&self.env, &mut wtxn);
wtxn.commit()?;

View File

@@ -2,10 +2,18 @@ use heed::{types::Str, Database, Env, RwTxn};
use super::tables;
pub const TABLES_COUNT: u32 = 1;
pub const TABLES_COUNT: u32 = 2;
pub fn create_users_table(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> {
let _: tables::users::UsersTable = env.create_database(wtxn, None)?;
let _: tables::users::UsersTable =
env.create_database(wtxn, Some(tables::users::USERS_TABLE))?;
Ok(())
}
pub fn create_sessions_table(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> {
let _: tables::sessions::SessionsTable =
env.create_database(wtxn, Some(tables::sessions::SESSIONS_TABLE))?;
Ok(())
}

View File

@@ -1 +1,2 @@
pub mod sessions;
pub mod users;

View File

@@ -0,0 +1,42 @@
use std::{borrow::Cow, time::SystemTime};
use postcard::{from_bytes, to_allocvec};
use pubky_common::timestamp::Timestamp;
use serde::{Deserialize, Serialize};
use heed::{types::Bytes, BoxedError, BytesDecode, BytesEncode, Database};
use pkarr::PublicKey;
extern crate alloc;
use alloc::vec::Vec;
/// session secret => Session.
pub type SessionsTable = Database<Bytes, Session>;
pub const SESSIONS_TABLE: &str = "sessions";
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct Session {
pub created_at: u64,
pub name: String,
}
impl<'a> BytesEncode<'a> for Session {
type EItem = Self;
fn bytes_encode(session: &Self::EItem) -> Result<Cow<[u8]>, BoxedError> {
let vec = to_allocvec(session)?;
Ok(Cow::Owned(vec))
}
}
impl<'a> BytesDecode<'a> for Session {
type DItem = Self;
fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, BoxedError> {
let sesison: Session = from_bytes(bytes).unwrap();
Ok(sesison)
}
}

View File

@@ -2,6 +2,7 @@ use axum::{
routing::{get, post, put},
Router,
};
use tower_cookies::CookieManagerLayer;
use tower_http::trace::TraceLayer;
use crate::server::AppState;
@@ -14,7 +15,9 @@ pub fn create_app(state: AppState) -> Router {
Router::new()
.route("/", get(root::handler))
.route("/:pubky", put(auth::signup))
.route("/:pubky/session", get(auth::session))
.route("/:pubky/*key", get(drive::put))
.layer(TraceLayer::new_for_http())
.layer(CookieManagerLayer::new())
.with_state(state)
}

View File

@@ -1,32 +1,102 @@
use axum::{extract::State, response::IntoResponse};
use axum::{
extract::{Request, State},
http::{HeaderMap, StatusCode},
response::IntoResponse,
routing::get,
Router,
};
use axum_extra::{headers::UserAgent, TypedHeader};
use bytes::Bytes;
use tower_cookies::{Cookie, Cookies};
use pubky_common::timestamp::Timestamp;
use pubky_common::{
crypto::{random_bytes, random_hash},
timestamp::Timestamp,
};
use crate::{
database::tables::users::{User, UsersTable, USERS_TABLE},
error::Result,
database::tables::{
sessions::{Session, SessionsTable, SESSIONS_TABLE},
users::{User, UsersTable, USERS_TABLE},
},
error::{Error, Result},
extractors::Pubky,
server::AppState,
};
pub async fn signup(
State(state): State<AppState>,
TypedHeader(user_agent): TypedHeader<UserAgent>,
cookies: Cookies,
pubky: Pubky,
body: Bytes,
) -> Result<impl IntoResponse> {
state.verifier.verify(&body, pubky.public_key())?;
let public_key = pubky.public_key();
state.verifier.verify(&body, public_key)?;
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(),
public_key,
&User {
created_at: Timestamp::now().into_inner(),
},
)?;
let session_secret = random_bytes::<16>();
let sessions: SessionsTable = state
.db
.env
.open_database(&wtxn, Some(SESSIONS_TABLE))?
.expect("Sessions table already created");
// TODO: handle not having a user agent?
let session = &Session {
created_at: Timestamp::now().into_inner(),
name: user_agent.to_string(),
};
sessions.put(&mut wtxn, &session_secret, session)?;
cookies.add(Cookie::new(
public_key.to_string(),
base32::encode(base32::Alphabet::Crockford, &session_secret),
));
wtxn.commit()?;
Ok(())
}
pub async fn session(
State(state): State<AppState>,
TypedHeader(user_agent): TypedHeader<UserAgent>,
cookies: Cookies,
pubky: Pubky,
) -> Result<impl IntoResponse> {
if let Some(cookie) = cookies.get(&pubky.public_key().to_string()) {
let rtxn = state.db.env.read_txn()?;
let sessions: SessionsTable = state
.db
.env
.open_database(&rtxn, Some(SESSIONS_TABLE))?
.expect("Session table already created");
if let Some(session) = sessions.get(
&rtxn,
&base32::decode(base32::Alphabet::Crockford, cookie.value()).unwrap_or_default(),
)? {
rtxn.commit()?;
return Ok(());
};
rtxn.commit()?;
};
Err(Error::with_status(StatusCode::NOT_FOUND))
}

View File

@@ -63,6 +63,20 @@ impl PubkyClient {
Ok(())
}
/// Check the current sesison for a given Pubky in its homeserver.
pub fn session(&self, pubky: &PublicKey) -> Result<()> {
let (homeserver, mut url) = self.resolve_pubky_homeserver(pubky)?;
url.set_path(&format!("/{}/sesison", pubky));
let response = self
.request(HttpMethod::Get, &url)
.call()
.map_err(Box::new)?;
Ok(())
}
// === Private Methods ===
/// Publish the SVCB record for `_pubky.<public_key>`.

View File

@@ -1,6 +1,6 @@
use std::thread;
use pkarr::Keypair;
use pkarr::{Keypair, PublicKey};
use crate::{error::Result, PubkyClient};
@@ -28,4 +28,19 @@ impl PubkyClientAsync {
receiver.recv_async().await?
}
/// Async version of [PubkyClient::session]
pub async fn session(&self, pubky: &PublicKey) -> Result<()> {
let (sender, receiver) = flume::bounded::<Result<()>>(1);
let client = self.0.clone();
let pubky = pubky.clone();
thread::spawn(move || {
let result = client.session(&pubky);
sender.send(result)
});
receiver.recv_async().await?
}
}

View File

@@ -25,6 +25,8 @@ mod tests {
client
.signup(&keypair, &server.public_key().to_string())
.await
.unwrap()
.unwrap();
let session = client.session(&keypair.public_key()).await.unwrap();
}
}