Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/ffi #22

Merged
merged 26 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
979882b
feat(js): add common modules
Nuhvi Jul 24, 2024
ae4a34c
feat(pubky): add wasm mod
Nuhvi Jul 24, 2024
b036020
feat(js): successful _initial_ test of wasm in nodejs and browser
Nuhvi Jul 25, 2024
e407461
examples: add preinstall script
Nuhvi Jul 25, 2024
d81b623
test: add JS unit tests instead of nodejs examples
Nuhvi Jul 26, 2024
42156b1
feat(js): signup
Nuhvi Jul 26, 2024
cdfd6c3
refactor(pubky): refactor modules
Nuhvi Jul 26, 2024
e05a49c
fix: cargo clippy
Nuhvi Jul 26, 2024
c466ca5
refactor(pubky): share helper functions between rust and wasm
Nuhvi Jul 27, 2024
ba50429
feat(pubky): use reqwest and async instead of ureq
Nuhvi Jul 27, 2024
c40ba83
feat(pubky): enable get and put methods
Nuhvi Jul 27, 2024
dac2284
feat(homeserver): add in memory pkarr relay api for testing
Nuhvi Jul 28, 2024
d35c586
feat(pubky): start testing js package against local homeserver
Nuhvi Jul 28, 2024
3cfd876
refactor(pubky): remove unused flume
Nuhvi Jul 28, 2024
e0b5845
refactor(pubky): move pkarr logic to shared
Nuhvi Jul 28, 2024
3cc81a5
test(pubky): add headless testing instead of examples
Nuhvi Jul 28, 2024
d050866
feat(pubky): auth working everywhere except nodejs
Nuhvi Jul 28, 2024
9bef331
chore(pubky): slight size optimization for wasm bundle size
Nuhvi Jul 29, 2024
d64f610
feat(pubky): cookie jar for nodejs
Nuhvi Jul 29, 2024
ce2a00f
chore(pubky): add script to run testnet homeserver
Nuhvi Jul 29, 2024
bd5b44e
feat(pubky): add put/get methods for js
Nuhvi Jul 29, 2024
65f9404
fix(pubky): cookies jar in nodejs
Nuhvi Jul 29, 2024
bdd07f5
fix(pubky): cookies jar in nodejs
Nuhvi Jul 29, 2024
5b9a498
chore(pubky): clippy issues
Nuhvi Jul 29, 2024
75d8acd
feat(pubky): override default pkarr relays
Nuhvi Jul 29, 2024
59839c5
docs: fix some comments
Nuhvi Jul 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
357 changes: 239 additions & 118 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ members = [ "pubky","pubky-*"]

# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352
resolver = "2"

[profile.release]
lto = true
opt-level = 'z'
3 changes: 3 additions & 0 deletions pubky-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ rand = "0.8.5"
thiserror = "1.0.60"
postcard = { version = "1.0.8", features = ["alloc"] }
serde = { version = "1.0.204", features = ["derive"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.69"
4 changes: 2 additions & 2 deletions pubky-common/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,15 @@ mod tests {
let mut invalid = authn_signature.as_bytes().to_vec();
invalid[64..].copy_from_slice(&[0; 32]);

assert!(!verifier.verify(&invalid, &signer).is_ok())
assert!(verifier.verify(&invalid, &signer).is_err())
}

{
// Invalid signer
let mut invalid = authn_signature.as_bytes().to_vec();
invalid[0..32].copy_from_slice(&[0; 32]);

assert!(!verifier.verify(&invalid, &signer).is_ok())
assert!(verifier.verify(&invalid, &signer).is_err())
}
}
}
7 changes: 4 additions & 3 deletions pubky-common/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ mod tests {

#[test]
fn serialize() {
let mut session = Session::default();

session.user_agent = "foo".to_string();
let session = Session {
user_agent: "foo".to_string(),
..Default::default()
};

let serialized = session.serialize();

Expand Down
13 changes: 12 additions & 1 deletion pubky-common/src/timestamp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Monotonic unix timestamp in microseconds

use std::fmt::Display;
use std::time::SystemTime;
use std::{
ops::{Add, Sub},
sync::Mutex,
Expand All @@ -10,6 +9,9 @@ use std::{
use once_cell::sync::Lazy;
use rand::Rng;

#[cfg(not(target_arch = "wasm32"))]
use std::time::SystemTime;

/// ~4% chance of none of 10 clocks have matching id.
const CLOCK_MASK: u64 = (1 << 8) - 1;
const TIME_MASK: u64 = !0 >> 8;
Expand Down Expand Up @@ -162,6 +164,15 @@ fn system_time() -> u64 {
.as_micros() as u64
}

#[cfg(target_arch = "wasm32")]
/// Return the number of microseconds since [SystemTime::UNIX_EPOCH]
pub fn system_time() -> u64 {
// Won't be an issue for more than 5000 years!
(js_sys::Date::now() as u64 )
// Turn miliseconds to microseconds
* 1000
}

#[derive(thiserror::Error, Debug)]
pub enum TimestampError {
#[error("Invalid bytes length, Timestamp should be encoded as 8 bytes, got {0}")]
Expand Down
3 changes: 2 additions & 1 deletion pubky-homeserver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ edition = "2021"

[dependencies]
anyhow = "1.0.82"
axum = "0.7.5"
axum = { version = "0.7.5", features = ["macros"] }
axum-extra = { version = "0.9.3", features = ["typed-header", "async-read-body"] }
base32 = "0.5.1"
bytes = "1.6.1"
clap = { version = "4.5.11", features = ["derive"] }
dirs-next = "2.0.0"
flume = "0.11.0"
futures-util = "0.3.30"
Expand Down
19 changes: 10 additions & 9 deletions pubky-homeserver/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
//! Configuration for the server

use anyhow::{anyhow, Result};
use pkarr::Keypair;
use pkarr::{mainline::dht::DhtSettings, Keypair};
// use serde::{Deserialize, Serialize};
use std::{fmt::Debug, path::PathBuf};
use std::{fmt::Debug, path::PathBuf, time::Duration};

use pubky_common::timestamp::Timestamp;

const DEFAULT_HOMESERVER_PORT: u16 = 6287;
const DEFAULT_STORAGE_DIR: &str = "pubky";

/// Server configuration
///
/// The config is usually loaded from a file with [`Self::load`].
#[derive(
// Serialize, Deserialize,
Clone,
)]
pub struct Config {
port: Option<u16>,
bootstrap: Option<Vec<String>>,
domain: String,
pub port: Option<u16>,
pub bootstrap: Option<Vec<String>>,
pub domain: String,
/// Path to the storage directory
///
/// Defaults to a directory in the OS data directory
storage: Option<PathBuf>,
keypair: Keypair,
pub storage: Option<PathBuf>,
pub keypair: Keypair,
pub request_timeout: Option<Duration>,
}

impl Config {
Expand All @@ -50,6 +49,7 @@ impl Config {
Self {
bootstrap,
storage,
request_timeout: Some(Duration::from_millis(10)),
..Default::default()
}
}
Expand Down Expand Up @@ -93,6 +93,7 @@ impl Default for Config {
domain: "localhost".to_string(),
storage: None,
keypair: Keypair::random(),
request_timeout: None,
}
}
}
Expand Down
34 changes: 31 additions & 3 deletions pubky-homeserver/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
use anyhow::Result;
use pubky_homeserver::Homeserver;
use pkarr::{mainline::Testnet, Keypair};
use pubky_homeserver::{config::Config, Homeserver};

use clap::Parser;

#[derive(Parser, Debug)]
struct Cli {
/// [tracing_subscriber::EnvFilter]
#[clap(short, long)]
tracing_env_filter: Option<String>,
#[clap(long)]
testnet: bool,
}

#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();

tracing_subscriber::fmt()
.with_env_filter("pubky_homeserver=debug,tower_http=debug")
.with_env_filter(
args.tracing_env_filter
.unwrap_or("pubky_homeserver=debug,tower_http=debug".to_string()),
)
.init();

let server = Homeserver::start(Default::default()).await?;
let server = if args.testnet {
let testnet = Testnet::new(3);

Homeserver::start(Config {
port: Some(15411),
keypair: Keypair::from_secret_key(&[0_u8; 32]),
..Config::test(&testnet)
})
.await?
} else {
Homeserver::start(Default::default()).await?
};

server.run_until_done().await?;

Expand Down
22 changes: 19 additions & 3 deletions pubky-homeserver/src/routes.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
use std::sync::Arc;

use axum::{
extract::DefaultBodyLimit,
http::Method,
routing::{delete, get, post, put},
Router,
};
use tower_cookies::CookieManagerLayer;
use tower_http::trace::TraceLayer;
use tower_http::{
cors::{self, CorsLayer},
trace::TraceLayer,
};

use crate::server::AppState;

use self::pkarr::pkarr_router;

mod auth;
mod pkarr;
mod public;
mod root;

pub fn create_app(state: AppState) -> Router {
fn base(state: AppState) -> Router {
Router::new()
.route("/", get(root::handler))
.route("/:pubky", put(auth::signup))
Expand All @@ -21,10 +30,17 @@ pub fn create_app(state: AppState) -> Router {
.route("/:pubky/session", delete(auth::signout))
.route("/:pubky/*path", put(public::put))
.route("/:pubky/*path", get(public::get))
.layer(TraceLayer::new_for_http())
.layer(CookieManagerLayer::new())
// TODO: revisit if we enable streaming big payloads
// TODO: maybe add to a separate router (drive router?).
.layer(DefaultBodyLimit::max(16 * 1024))
.with_state(state)
}

pub fn create_app(state: AppState) -> Router {
base(state.clone())
// TODO: Only enable this for test environments?
.nest("/pkarr", pkarr_router(state))
.layer(CorsLayer::very_permissive())
.layer(TraceLayer::new_for_http())
}
29 changes: 25 additions & 4 deletions pubky-homeserver/src/routes/auth.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use axum::{
debug_handler,
extract::{Request, State},
http::{HeaderMap, StatusCode},
http::{uri::Scheme, HeaderMap, StatusCode, Uri},
response::IntoResponse,
Router,
};
use axum_extra::{headers::UserAgent, TypedHeader};
use bytes::Bytes;
use heed::BytesEncode;
use postcard::to_allocvec;
use tower_cookies::{Cookie, Cookies};
use tower_cookies::{cookie::SameSite, Cookie, Cookies};

use pubky_common::{
crypto::{random_bytes, random_hash},
Expand All @@ -26,16 +27,26 @@ use crate::{
server::AppState,
};

#[debug_handler]
pub async fn signup(
State(state): State<AppState>,
TypedHeader(user_agent): TypedHeader<UserAgent>,
cookies: Cookies,
pubky: Pubky,
uri: Uri,
body: Bytes,
) -> Result<impl IntoResponse> {
// TODO: Verify invitation link.
// TODO: add errors in case of already axisting user.
signin(State(state), TypedHeader(user_agent), cookies, pubky, body).await
signin(
State(state),
TypedHeader(user_agent),
cookies,
pubky,
uri,
body,
)
.await
}

pub async fn session(
Expand All @@ -57,6 +68,7 @@ pub async fn session(
let session = session.to_owned();
rtxn.commit()?;

// TODO: add content-type
return Ok(session);
};

Expand Down Expand Up @@ -95,6 +107,7 @@ pub async fn signin(
TypedHeader(user_agent): TypedHeader<UserAgent>,
cookies: Cookies,
pubky: Pubky,
uri: Uri,
body: Bytes,
) -> Result<impl IntoResponse> {
let public_key = pubky.public_key();
Expand Down Expand Up @@ -135,7 +148,15 @@ pub async fn signin(

sessions.put(&mut wtxn, &session_secret, &session.serialize())?;

cookies.add(Cookie::new(public_key.to_string(), session_secret));
let mut cookie = Cookie::new(public_key.to_string(), session_secret);
cookie.set_path("/");
if *uri.scheme().unwrap_or(&Scheme::HTTP) == Scheme::HTTPS {
cookie.set_secure(true);
cookie.set_same_site(SameSite::None);
}
cookie.set_http_only(true);

cookies.add(cookie);

wtxn.commit()?;

Expand Down
61 changes: 61 additions & 0 deletions pubky-homeserver/src/routes/pkarr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::{collections::HashMap, sync::RwLock};

use axum::{
body::{Body, Bytes},
extract::State,
http::StatusCode,
response::IntoResponse,
routing::{get, put},
Router,
};
use futures_util::stream::StreamExt;

use pkarr::{PublicKey, SignedPacket};
use tracing::debug;

use crate::{
error::{Error, Result},
extractors::Pubky,
server::AppState,
};

/// Pkarr relay, helpful for testing.
///
/// For real productioin, you should use a [production ready
/// relay](https://github.com/pubky/pkarr/server).
pub fn pkarr_router(state: AppState) -> Router {
Router::new()
.route("/:pubky", put(pkarr_put))
.route("/:pubky", get(pkarr_get))
.with_state(state)
}

pub async fn pkarr_put(
State(mut state): State<AppState>,
pubky: Pubky,
body: Body,
) -> Result<impl IntoResponse> {
let mut bytes = Vec::with_capacity(1104);

let mut stream = body.into_data_stream();

while let Some(chunk) = stream.next().await {
bytes.extend_from_slice(&chunk?)
}

let public_key = pubky.public_key().to_owned();

let signed_packet = SignedPacket::from_relay_payload(&public_key, &Bytes::from(bytes))?;

state.pkarr_client.publish(&signed_packet).await?;

Ok(())
}

pub async fn pkarr_get(State(state): State<AppState>, pubky: Pubky) -> Result<impl IntoResponse> {
if let Some(signed_packet) = state.pkarr_client.resolve(pubky.public_key()).await? {
return Ok(signed_packet.to_relay_payload());
}

Err(Error::with_status(StatusCode::NOT_FOUND))
}
Loading
Loading