gourami

[UNMAINTAINED] Activitypub server in Rust
Log | Files | Refs | README | LICENSE

commit b54bd3ec0177f38add12e511f067dbcb0f04d871
parent c03cbb543de11116c3ea9f00030be6d68d298df9
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Wed, 22 Apr 2020 21:52:46 -0500

Cleanup repository

Diffstat:
MCargo.toml | 26+++++++++++++-------------
MREADME.md | 38+++++++++++---------------------------
Dauth.rs | 1-
Dsession.rs | 90-------------------------------------------------------------------------------
Msrc/ap.rs | 5++---
Msrc/db/note.rs | 6------
Msrc/db/user.rs | 3+--
Msrc/lib.rs | 17++++++++++++-----
Dsrc/routes.rs | 1-
Msrc/session.rs | 7+------
Dtemplates.rs | 0
11 files changed, 40 insertions(+), 154 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -6,23 +6,23 @@ edition = "2018" [dependencies] activitystreams = "0.5.0" -askama = "0.8" -bcrypt = "0.7" -cookie = "0.13" -chrono = "*" -diesel = { version = "1.4", features = ["sqlite", "r2d2"] } -env_logger = "0.7" +askama = "0.8.0" +bcrypt = "0.7.0" +cookie = "0.13.3" +chrono = "0.4.11" +diesel = { version = "1.4.4", features = ["sqlite", "r2d2"] } +env_logger = "0.7.1" lazy_static = "1.4.0" -log = "0.4" -rand = "0.7" -reqwest = "0.10" +log = "0.4.8" +rand = "0.7.3" +reqwest = "0.10.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = { version = "0.2", features = ["macros"] } -warp = "0.2" +tokio = { version = "0.2.17", features = ["macros"] } +warp = "0.2.2" hyper = "0.13" -regex = "1.3" -ammonia = "3" +regex = "1.3.6" +ammonia = "3.1.0" maplit = "1.0.2" [dev-dependencies] diff --git a/README.md b/README.md @@ -1,38 +1,22 @@ -# gourami +gourami -## Dependencies: - -sqlite3 +An intentionally small, ultra-lightweight social media network (ActivityPub integration TBD) -## Goals: +## Philosophy -* Activity-pub compatible -* UX Brutalism -- Expose, rather than hide, the fundamental components of HTML and the Web. The user interface may be jarring to some who are used to the modern web. -* Lightweight -- low energy usage, low resource usage. This makes the service much cheaper to host and enables it to run in contexts where other services may not be able to. -* Increased control over -* Completely free and open source software -* Highly customizable user experience -* Configurable for offline / local network usage -* Extreme accessibility +Link to doc, summary here -## Maybe goals: - -* Email and SMS interface +## Dependencies: +sqlite3 -## Non-goals: +## Installation -* Scalability -* Certain anti-abuse and anti-spam features. This is designed to be used as a small-scale service with active moderation, not a service with open public registration. +### Through Docker +TBD -## Some inspiration/influences: +### Otherwise -* Jenny Odell's discussion of adding context to social media in *How to Do Nothing* -* https://runyourown.social/ -* https://joinmastodon.org/ -* https://sourcehut.org/ -* https://100r.co/site/mission.html -* https://solar.lowtechmagazine.com/2020/01/how-sustainable-is-a-solar-powered-website.html -* https://www.nycmesh.net/ +## Deployment diff --git a/auth.rs b/auth.rs @@ -1 +0,0 @@ -// login, registration, etc diff --git a/session.rs b/session.rs @@ -1,90 +0,0 @@ -use db::user::User; -use log::{debug, error}; -use rand::thread_rng; -use warp::filters::{cookie, BoxedFilter}; - -pub struct Session { - id: Option<i32>, - user: Option<User>, -} - -// TODO -- figure out if database pooling is strictly necessary for security - -impl Session { - /// Attempt to authenticate a user for this session. - /// - /// If the username and password is valid, create and return a session key. - /// If authentication fails, simply return None. - pub fn authenticate(&mut self, conn: &SqliteConnection, username: &str, password: &str) -> Some(String) { - if let Some(user) = User::authenticate(self.db(), username, password) { - debug!("User authenticated"); - let random_key = thread_rng().sample_iter(&Alphanumeric).take(48).collect() - use crate::schema::sessions::dsl::*; - let result = diesel::insert_into(sessions) - .values((user_id.eq(user.id), cookie.eq(&secret))) - .returning(id) - .get_results(conn); - if let Ok([a]) = result.as_ref().map(|v| &**v) { - self.id = Some(*a); - self.user = Some(user); - return Some(secret); - } else { - error!( - "Failed to create session for {}: {:?}", - user.username, result, - ); - } - } - None - } - /// Get a Session from a database pool and a session key. - /// - /// The session key is checked against the database, and the - /// matching session is loaded. - pub fn from_key(conn: &SqliteConnection, sessionkey: Option<&str>) -> Self { - use crate::schema::sessions::dsl as s; - use crate::schema::users::dsl as u; - let (id, user) = sessionkey - .and_then(|sessionkey| { - u::users - .inner_join(s::sessions) - .select((s::id, u::username, u::realname))) - .filter(s::cookie.eq(&sessionkey)) - .first::<(i32, User)>(conn) - .ok() - }) - .map(|(i, u)| (Some(i), Some(u))) - .unwrap_or((None, None)); - - debug!("Got: #{:?} {:?}", id, user); - Session { db, id, user } - } - /// Clear the part of this session that is session-specific. - pub fn clear(conn: &SqliteConnection) { - use crate::schema::sessions::dsl as s; - if let Some(session_id) = self.id { - diesel::delete(s::sessions.filter(s::id.eq(session_id))) - .execute(self.db()) - .map_err(|e| { - error!( - "Failed to delete session {}: {:?}", - session_id, e - ); - }) - .ok(); - } - self.id = None; - self.user = None; - } -} - -pub fn create_session_filter(conn: &SqliteConnection) -> BoxedFilter<(Session,)> { - warp::any() - .and(cookie::optional("EXAUTH")) - .and_then(move |key: Option<String>| { - let key = key.as_ref().map(|s| &**s); - Ok(Session::from_key(conn, key)), - } - }) - .boxed() -} diff --git a/src/ap.rs b/src/ap.rs @@ -1,6 +1,5 @@ -use log::{debug, info}; -use serde_json::{Result, Value}; -use std::error::Error; +use log::{debug}; +use serde_json::{Value}; use activitystreams::activity::{Create, Accept, Follow, Reject, Announce, Delete, Activity}; // gonna be big diff --git a/src/db/note.rs b/src/db/note.rs @@ -1,13 +1,7 @@ -use chrono; -use std::collections::HashSet; -use diesel::sqlite::SqliteConnection; use maplit::hashset; -use diesel::deserialize::{Queryable}; use super::schema::notes; -use diesel::prelude::*; use serde::{Deserialize, Serialize}; use regex::Regex; -use std::iter::FromIterator; use ammonia; // Statuses are note activitystream object diff --git a/src/db/user.rs b/src/db/user.rs @@ -1,8 +1,7 @@ use diesel::sqlite::SqliteConnection; -use diesel::deserialize::{Queryable}; use super::schema::users; use diesel::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize}; use bcrypt; #[derive(Debug, Clone, Default, Queryable, Deserialize)] diff --git a/src/lib.rs b/src/lib.rs @@ -10,8 +10,6 @@ use std::convert::Infallible; use warp::{Reply, Filter, Rejection}; use warp::http; use warp::hyper::Body; -use warp::reply::{Response}; -use warp::reject::{custom, not_found}; use hyper; use askama::Template; @@ -22,9 +20,9 @@ use db::user::{RegistrationKey, User, NewUser}; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; use diesel::insert_into; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize}; use session::{Session}; -use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; +use diesel::r2d2::{ConnectionManager, Pool}; type SqlitePool = Pool<ConnectionManager<SqliteConnection>>; @@ -194,7 +192,9 @@ fn do_register(form: RegisterForm, query_params: serde_json::Value) -> impl Repl let conn = &POOL.get().unwrap(); use db::schema::users::dsl::*; if let Some(k) = query_params.get("key") { - let keyed = RegistrationKey::is_valid(&POOL.get().unwrap(), &k.as_str().unwrap()); + let k_string = &k.as_str().unwrap(); + let keyed = RegistrationKey::is_valid(conn, k_string); + RegistrationKey::clear_key(conn, k_string); if keyed { let hash = bcrypt::hash(&form.password, bcrypt::DEFAULT_COST).unwrap(); let new_user = NewUser {username: &form.username, password: &hash, email: &form.email}; @@ -359,6 +359,13 @@ async fn error_page(err: Rejection) -> Result<impl Reply, Infallible>{ Ok(render_template(&ErrorTemplate{global: Global::from_session(None), page: "error", error_message: "You do not have access to this page."})) } + +// Url query +#[derive(Deserialize)] +struct Page { + page_num: i32 +} + pub async fn run_server() { env_logger::init(); // cors filters etc diff --git a/src/routes.rs b/src/routes.rs @@ -1 +0,0 @@ -// TODO add routes here diff --git a/src/session.rs b/src/session.rs @@ -29,12 +29,7 @@ impl Session { let result = diesel::insert_into(sessions) .values((user_id.eq(user.id), cookie.eq(&secret))) .execute(conn); - let session_id = sessions.select(id) - .filter(cookie.eq(&secret)) - .first::<i32>(conn); - if let Ok(s_id) = result { - // self.id = Some(s_id as i32); - // self.user = Some(user); + if let Ok(_) = result { return Some(secret); } else { error!( diff --git a/templates.rs b/templates.rs