Skip to content

Commit

Permalink
feat(homeserver): return 401 forbidden if you try to write to someone…
Browse files Browse the repository at this point in the history
… else's drive
  • Loading branch information
Nuhvi committed Jul 30, 2024
1 parent e9bb104 commit b315294
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 67 deletions.
61 changes: 1 addition & 60 deletions pubky-homeserver/src/database.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fs;

use std::path::Path;

use bytes::Bytes;
Expand Down Expand Up @@ -32,66 +33,6 @@ impl DB {

Ok(db)
}

pub fn put_entry(
&mut self,
public_key: &PublicKey,
path: &str,
rx: flume::Receiver<Bytes>,
) -> anyhow::Result<()> {
let mut wtxn = self.env.write_txn()?;

let mut hasher = Hasher::new();
let mut bytes = vec![];
let mut length = 0;

while let Ok(chunk) = rx.recv() {
hasher.update(&chunk);
bytes.extend_from_slice(&chunk);
length += chunk.len();
}

let hash = hasher.finalize();

self.tables.blobs.put(&mut wtxn, hash.as_bytes(), &bytes)?;

let mut entry = Entry::new();

entry.set_content_hash(hash);
entry.set_content_length(length);

let mut key = vec![];
key.extend_from_slice(public_key.as_bytes());
key.extend_from_slice(path.as_bytes());

self.tables.entries.put(&mut wtxn, &key, &entry.serialize());

wtxn.commit()?;

Ok(())
}

pub fn get_blob(
&mut self,
public_key: &PublicKey,
path: &str,
) -> anyhow::Result<Option<Bytes>> {
let mut rtxn = self.env.read_txn()?;

let mut key = vec![];
key.extend_from_slice(public_key.as_bytes());
key.extend_from_slice(path.as_bytes());

if let Some(bytes) = self.tables.entries.get(&rtxn, &key)? {
let entry = Entry::deserialize(bytes)?;

if let Some(blob) = self.tables.blobs.get(&rtxn, entry.content_hash())? {
return Ok(Some(Bytes::from(blob.to_vec())));
};
};

Ok(None)
}
}

#[cfg(test)]
Expand Down
29 changes: 29 additions & 0 deletions pubky-homeserver/src/database/tables/blobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,37 @@ use heed::{
types::{Bytes, Str},
BoxedError, BytesDecode, BytesEncode, Database,
};
use pkarr::PublicKey;

use crate::database::DB;

use super::entries::Entry;

/// hash of the blob => bytes.
pub type BlobsTable = Database<Bytes, Bytes>;

pub const BLOBS_TABLE: &str = "blobs";

impl DB {
pub fn get_blob(
&mut self,
public_key: &PublicKey,
path: &str,
) -> anyhow::Result<Option<bytes::Bytes>> {
let mut rtxn = self.env.read_txn()?;

let mut key = vec![];
key.extend_from_slice(public_key.as_bytes());
key.extend_from_slice(path.as_bytes());

if let Some(bytes) = self.tables.entries.get(&rtxn, &key)? {
let entry = Entry::deserialize(bytes)?;

if let Some(blob) = self.tables.blobs.get(&rtxn, entry.content_hash())? {
return Ok(Some(bytes::Bytes::from(blob.to_vec())));
};
};

Ok(None)
}
}
48 changes: 47 additions & 1 deletion pubky-homeserver/src/database/tables/entries.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use pkarr::PublicKey;
use postcard::{from_bytes, to_allocvec};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, time::SystemTime};
Expand All @@ -7,13 +8,58 @@ use heed::{
BoxedError, BytesDecode, BytesEncode, Database,
};

use pubky_common::{crypto::Hash, timestamp::Timestamp};
use pubky_common::{
crypto::{Hash, Hasher},
timestamp::Timestamp,
};

use crate::database::DB;

/// full_path(pubky/*path) => Entry.
pub type EntriesTable = Database<Bytes, Bytes>;

pub const ENTRIES_TABLE: &str = "entries";

impl DB {
pub fn put_entry(
&mut self,
public_key: &PublicKey,
path: &str,
rx: flume::Receiver<bytes::Bytes>,
) -> anyhow::Result<()> {
let mut wtxn = self.env.write_txn()?;

let mut hasher = Hasher::new();
let mut bytes = vec![];
let mut length = 0;

while let Ok(chunk) = rx.recv() {
hasher.update(&chunk);
bytes.extend_from_slice(&chunk);
length += chunk.len();
}

let hash = hasher.finalize();

self.tables.blobs.put(&mut wtxn, hash.as_bytes(), &bytes)?;

let mut entry = Entry::new();

entry.set_content_hash(hash);
entry.set_content_length(length);

let mut key = vec![];
key.extend_from_slice(public_key.as_bytes());
key.extend_from_slice(path.as_bytes());

self.tables.entries.put(&mut wtxn, &key, &entry.serialize());

wtxn.commit()?;

Ok(())
}
}

#[derive(Clone, Default, Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct Entry {
/// Encoding version
Expand Down
45 changes: 45 additions & 0 deletions pubky-homeserver/src/database/tables/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,53 @@ use heed::{
types::{Bytes, Str},
BoxedError, BytesDecode, BytesEncode, Database,
};
use pkarr::PublicKey;
use pubky_common::session::Session;
use serde::Deserialize;
use tower_cookies::Cookies;

use crate::database::DB;

/// session secret => Session.
pub type SessionsTable = Database<Str, Bytes>;

pub const SESSIONS_TABLE: &str = "sessions";

impl DB {
pub fn get_session(
&mut self,
cookies: Cookies,
public_key: &PublicKey,
path: &str,
) -> anyhow::Result<Option<Session>> {
if let Some(bytes) = self.get_session_bytes(cookies, public_key, path)? {
return Ok(Some(Session::deserialize(&bytes)?));
};

Ok(None)
}

pub fn get_session_bytes(
&mut self,
cookies: Cookies,
public_key: &PublicKey,
path: &str,
) -> anyhow::Result<Option<Vec<u8>>> {
if let Some(cookie) = cookies.get(&public_key.to_string()) {
let rtxn = self.env.read_txn()?;

let sessions: SessionsTable = self
.env
.open_database(&rtxn, Some(SESSIONS_TABLE))?
.expect("Session table already created");

let session = sessions.get(&rtxn, cookie.value())?.map(|s| s.to_vec());

rtxn.commit()?;

return Ok(session);
};

Ok(None)
}
}
27 changes: 21 additions & 6 deletions pubky-homeserver/src/routes/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use axum::{
};
use axum_extra::body::AsyncReadBody;
use futures_util::stream::StreamExt;
use tower_cookies::Cookies;

use tracing::debug;

Expand All @@ -26,9 +27,27 @@ pub async fn put(
State(mut state): State<AppState>,
pubky: Pubky,
path: EntryPath,
cookies: Cookies,
mut body: Body,
) -> Result<impl IntoResponse> {
// TODO: return an error if path does not start with '/pub/'
let public_key = pubky.public_key().clone();
let path = path.as_str().to_string();

// TODO: can we move this logic to the extractor or a layer
// to perform this validation?
let session = state
.db
.get_session(cookies, &public_key, &path)?
.ok_or(Error::with_status(StatusCode::UNAUTHORIZED))?;

if !path.starts_with("pub/") {
return Err(Error::new(
StatusCode::FORBIDDEN,
"Writing to directories other than '/pub/' is forbidden".into(),
));
}

// TODO: should we forbid paths ending with `/`?

let mut stream = body.into_data_stream();

Expand All @@ -41,11 +60,7 @@ pub async fn put(
// to stream this to filesystem, and keep track of any failed
// writes to GC these files later.

let public_key = pubky.public_key();

// TODO: Authorize

state.db.put_entry(public_key, path.as_str(), rx);
state.db.put_entry(&public_key, &path, rx);

Ok(())
});
Expand Down

0 comments on commit b315294

Please sign in to comment.