From 463cacdf72de9b8cc291c68062e6447f3911493c Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 9 Apr 2019 11:35:16 -0700 Subject: [PATCH 1/7] Adding slur filter. - Fixes #45 --- server/Cargo.lock | 1 + server/Cargo.toml | 3 ++- server/src/lib.rs | 33 +++++++++++++++++++++----- server/src/websocket_server/server.rs | 34 ++++++++++++++++++++++++--- ui/src/components/community-form.tsx | 1 + ui/src/components/login.tsx | 2 +- ui/src/components/post-form.tsx | 2 ++ 7 files changed, 65 insertions(+), 11 deletions(-) diff --git a/server/Cargo.lock b/server/Cargo.lock index ce4e175a7..36d2a9014 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1359,6 +1359,7 @@ dependencies = [ "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "jsonwebtoken 5.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/server/Cargo.toml b/server/Cargo.toml index fd3d57730..93bd6acb2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -24,4 +24,5 @@ rand = "0.6.5" strum = "0.14.0" strum_macros = "0.14.0" jsonwebtoken = "*" -regex = "1" +regex = "*" +lazy_static = "*" diff --git a/server/src/lib.rs b/server/src/lib.rs index 9cdbd33ec..814363b49 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -12,7 +12,7 @@ pub extern crate jsonwebtoken; pub extern crate bcrypt; pub extern crate regex; #[macro_use] pub extern crate strum_macros; - +#[macro_use] pub extern crate lazy_static; pub mod schema; pub mod apub; pub mod actions; @@ -89,21 +89,42 @@ pub fn naive_now() -> NaiveDateTime { } pub fn is_email_regex(test: &str) -> bool { - let re = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); - re.is_match(test) + EMAIL_REGEX.is_match(test) +} + +pub fn remove_slurs(test: &str) -> String { + SLUR_REGEX.replace_all(test, "*removed*").to_string() +} + +pub fn has_slurs(test: &str) -> bool { + SLUR_REGEX.is_match(test) } #[cfg(test)] mod tests { - use {Settings, is_email_regex}; + use {Settings, is_email_regex, remove_slurs, has_slurs}; #[test] fn test_api() { assert_eq!(Settings::get().api_endpoint(), "http://0.0.0.0/api/v1"); } - #[test] - fn test_email() { + #[test] fn test_email() { assert!(is_email_regex("gush@gmail.com")); assert!(!is_email_regex("nada_neutho")); } + + #[test] fn test_slur_filter() { + let test = "coons test dindu ladyboy tranny. This is a bunch of other safe text.".to_string(); + let slur_free = "No slurs here"; + assert_eq!(remove_slurs(&test), "*removed* test *removed* *removed* *removed*. This is a bunch of other safe text.".to_string()); + assert!(has_slurs(&test)); + assert!(!has_slurs(slur_free)); + } } + + +lazy_static! { + static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); + static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|\bnig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?))").unwrap(); +} + diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index 92542d0a7..a946c9e07 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -10,7 +10,7 @@ use serde_json::{Value}; use bcrypt::{verify}; use std::str::FromStr; -use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType}; +use {Crud, Joinable, Likeable, Followable, establish_connection, naive_now, SortType, has_slurs, remove_slurs}; use actions::community::*; use actions::user::*; use actions::post::*; @@ -541,6 +541,10 @@ impl Perform for Register { return self.error("Passwords do not match."); } + if has_slurs(&self.username) { + return self.error("No slurs"); + } + // Register the new user let user_form = UserForm { name: self.username.to_owned(), @@ -587,6 +591,12 @@ impl Perform for CreateCommunity { } }; + if has_slurs(&self.name) || + has_slurs(&self.title) || + (self.description.is_some() && has_slurs(&self.description.to_owned().unwrap())) { + return self.error("No slurs"); + } + let user_id = claims.id; // When you create a community, make sure the user becomes a moderator and a follower @@ -716,6 +726,11 @@ impl Perform for CreatePost { } }; + if has_slurs(&self.name) || + (self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) { + return self.error("No slurs"); + } + let user_id = claims.id; let post_form = PostForm { @@ -894,8 +909,10 @@ impl Perform for CreateComment { let user_id = claims.id; + let content_slurs_removed = remove_slurs(&self.content.to_owned()); + let comment_form = CommentForm { - content: self.content.to_owned(), + content: content_slurs_removed, parent_id: self.parent_id.to_owned(), post_id: self.post_id, creator_id: user_id, @@ -976,8 +993,10 @@ impl Perform for EditComment { return self.error("Incorrect creator."); } + let content_slurs_removed = remove_slurs(&self.content.to_owned()); + let comment_form = CommentForm { - content: self.content.to_owned(), + content: content_slurs_removed, parent_id: self.parent_id, post_id: self.post_id, creator_id: user_id, @@ -1197,6 +1216,11 @@ impl Perform for EditPost { fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + if has_slurs(&self.name) || + (self.body.is_some() && has_slurs(&self.body.to_owned().unwrap())) { + return self.error("No slurs"); + } + let conn = establish_connection(); let claims = match Claims::decode(&self.auth) { @@ -1264,6 +1288,10 @@ impl Perform for EditCommunity { fn perform(&self, chat: &mut ChatServer, addr: usize) -> String { + if has_slurs(&self.name) || has_slurs(&self.title) { + return self.error("No slurs"); + } + let conn = establish_connection(); let claims = match Claims::decode(&self.auth) { diff --git a/ui/src/components/community-form.tsx b/ui/src/components/community-form.tsx index b5b222c64..056c29dca 100644 --- a/ui/src/components/community-form.tsx +++ b/ui/src/components/community-form.tsx @@ -155,6 +155,7 @@ export class CommunityForm extends Component {
- +
diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 9845a1b1a..03ace3802 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -151,7 +151,9 @@ export class PostForm extends Component { parseMessage(msg: any) { let op: UserOperation = msgOp(msg); if (msg.error) { + alert(msg.error); this.state.loading = false; + this.setState(this.state); return; } else if (op == UserOperation.ListCommunities) { let res: ListCommunitiesResponse = msg; From ab9cdd990d67710f031c97a9069992b3088f16cb Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 9 Apr 2019 14:21:19 -0700 Subject: [PATCH 2/7] Styling, sidebar message. --- server/src/websocket_server/server.rs | 36 +++++++++++++-------------- ui/src/components/communities.tsx | 2 +- ui/src/components/main.tsx | 30 ++++++++++++++++------ ui/src/components/navbar.tsx | 6 ++--- ui/src/components/post.tsx | 6 ++--- ui/src/components/sidebar.tsx | 2 +- ui/src/main.css | 9 +++++++ ui/src/services/UserService.ts | 4 +-- 8 files changed, 59 insertions(+), 36 deletions(-) diff --git a/server/src/websocket_server/server.rs b/server/src/websocket_server/server.rs index a946c9e07..42124d2d6 100644 --- a/server/src/websocket_server/server.rs +++ b/server/src/websocket_server/server.rs @@ -391,25 +391,25 @@ impl Handler for ChatServer { let json: Value = serde_json::from_str(&msg.msg) .expect("Couldn't parse message"); - let data: &Value = &json["data"]; + let data = &json["data"].to_string(); let op = &json["op"].as_str().unwrap(); let user_operation: UserOperation = UserOperation::from_str(&op).unwrap(); let res: String = match user_operation { UserOperation::Login => { - let login: Login = serde_json::from_str(&data.to_string()).unwrap(); + let login: Login = serde_json::from_str(data).unwrap(); login.perform(self, msg.id) }, UserOperation::Register => { - let register: Register = serde_json::from_str(&data.to_string()).unwrap(); + let register: Register = serde_json::from_str(data).unwrap(); register.perform(self, msg.id) }, UserOperation::CreateCommunity => { - let create_community: CreateCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let create_community: CreateCommunity = serde_json::from_str(data).unwrap(); create_community.perform(self, msg.id) }, UserOperation::ListCommunities => { - let list_communities: ListCommunities = serde_json::from_str(&data.to_string()).unwrap(); + let list_communities: ListCommunities = serde_json::from_str(data).unwrap(); list_communities.perform(self, msg.id) }, UserOperation::ListCategories => { @@ -417,55 +417,55 @@ impl Handler for ChatServer { list_categories.perform(self, msg.id) }, UserOperation::CreatePost => { - let create_post: CreatePost = serde_json::from_str(&data.to_string()).unwrap(); + let create_post: CreatePost = serde_json::from_str(data).unwrap(); create_post.perform(self, msg.id) }, UserOperation::GetPost => { - let get_post: GetPost = serde_json::from_str(&data.to_string()).unwrap(); + let get_post: GetPost = serde_json::from_str(data).unwrap(); get_post.perform(self, msg.id) }, UserOperation::GetCommunity => { - let get_community: GetCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let get_community: GetCommunity = serde_json::from_str(data).unwrap(); get_community.perform(self, msg.id) }, UserOperation::CreateComment => { - let create_comment: CreateComment = serde_json::from_str(&data.to_string()).unwrap(); + let create_comment: CreateComment = serde_json::from_str(data).unwrap(); create_comment.perform(self, msg.id) }, UserOperation::EditComment => { - let edit_comment: EditComment = serde_json::from_str(&data.to_string()).unwrap(); + let edit_comment: EditComment = serde_json::from_str(data).unwrap(); edit_comment.perform(self, msg.id) }, UserOperation::CreateCommentLike => { - let create_comment_like: CreateCommentLike = serde_json::from_str(&data.to_string()).unwrap(); + let create_comment_like: CreateCommentLike = serde_json::from_str(data).unwrap(); create_comment_like.perform(self, msg.id) }, UserOperation::GetPosts => { - let get_posts: GetPosts = serde_json::from_str(&data.to_string()).unwrap(); + let get_posts: GetPosts = serde_json::from_str(data).unwrap(); get_posts.perform(self, msg.id) }, UserOperation::CreatePostLike => { - let create_post_like: CreatePostLike = serde_json::from_str(&data.to_string()).unwrap(); + let create_post_like: CreatePostLike = serde_json::from_str(data).unwrap(); create_post_like.perform(self, msg.id) }, UserOperation::EditPost => { - let edit_post: EditPost = serde_json::from_str(&data.to_string()).unwrap(); + let edit_post: EditPost = serde_json::from_str(data).unwrap(); edit_post.perform(self, msg.id) }, UserOperation::EditCommunity => { - let edit_community: EditCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let edit_community: EditCommunity = serde_json::from_str(data).unwrap(); edit_community.perform(self, msg.id) }, UserOperation::FollowCommunity => { - let follow_community: FollowCommunity = serde_json::from_str(&data.to_string()).unwrap(); + let follow_community: FollowCommunity = serde_json::from_str(data).unwrap(); follow_community.perform(self, msg.id) }, UserOperation::GetFollowedCommunities => { - let followed_communities: GetFollowedCommunities = serde_json::from_str(&data.to_string()).unwrap(); + let followed_communities: GetFollowedCommunities = serde_json::from_str(data).unwrap(); followed_communities.perform(self, msg.id) }, UserOperation::GetUserDetails => { - let get_user_details: GetUserDetails = serde_json::from_str(&data.to_string()).unwrap(); + let get_user_details: GetUserDetails = serde_json::from_str(data).unwrap(); get_user_details.perform(self, msg.id) }, // _ => { diff --git a/ui/src/components/communities.tsx b/ui/src/components/communities.tsx index cf42238e1..268aa1151 100644 --- a/ui/src/components/communities.tsx +++ b/ui/src/components/communities.tsx @@ -45,7 +45,7 @@ export class Communities extends Component { render() { return ( -
+
{this.state.loading ?

:
diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 477eec65e..8faf858ae 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -5,7 +5,7 @@ import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { PostListings } from './post-listings'; -import { msgOp } from '../utils'; +import { msgOp, repoUrl } from '../utils'; interface State { subscribedCommunities: Array; @@ -46,17 +46,15 @@ export class Main extends Component { return (
-
+
-
-

A Landing message

- {UserService.Instance.loggedIn && +
+ {UserService.Instance.loggedIn ?
{this.state.loading ? -

: +

:
-

Subscribed forums

    {this.state.subscribedCommunities.map(community => @@ -65,7 +63,8 @@ export class Main extends Component {
} -
+
: + this.landing() }
@@ -73,6 +72,21 @@ export class Main extends Component { ) } + landing() { + return ( +
+

Welcome to + + LemmyBeta +

+

Lemmy is a link aggregator / reddit alternative, intended to work in the fediverse.

+

Its self-hostable, has live-updating comment threads, and is tiny (~80kB). Federation into the ActivityPub network is on the roadmap.

+

This is a very early beta version, and a lot of features are currently broken or missing.

+

Suggest new features or report bugs here.

+

Made with Rust, Actix, Inferno, Typescript.

+
+ ) + } parseMessage(msg: any) { console.log(msg); diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index ca0c5a2a2..a9d95362e 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -25,7 +25,7 @@ export class Navbar extends Component { // Subscribe to user changes UserService.Instance.sub.subscribe(user => { - let loggedIn: boolean = user !== null; + let loggedIn: boolean = user !== undefined; this.setState({isLoggedIn: loggedIn}); }); } @@ -40,7 +40,7 @@ export class Navbar extends Component { // TODO toggle css collapse navbar() { return ( -
: - Login + Login / Sign up }
diff --git a/ui/src/components/post.tsx b/ui/src/components/post.tsx index f36893f69..5ca3f7707 100644 --- a/ui/src/components/post.tsx +++ b/ui/src/components/post.tsx @@ -101,17 +101,17 @@ export class Post extends Component { sortRadios() { return (
-
@@ -72,6 +82,19 @@ export class Main extends Component { ) } + trendingCommunities() { + return ( +
+

Trending forums

+
    + {this.state.trendingCommunities.map(community => +
  • {community.name}
  • + )} +
+
+ ) + } + landing() { return (
@@ -99,6 +122,11 @@ export class Main extends Component { this.state.subscribedCommunities = res.communities; this.state.loading = false; this.setState(this.state); + } else if (op == UserOperation.ListCommunities) { + let res: ListCommunitiesResponse = msg; + this.state.trendingCommunities = res.communities; + this.state.loading = false; + this.setState(this.state); } } } diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index 03ace3802..67a3f42e0 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -1,7 +1,7 @@ import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; -import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces'; +import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse, ListCommunitiesForm, SortType } from '../interfaces'; import { WebSocketService } from '../services'; import { msgOp } from '../utils'; import * as autosize from 'autosize'; @@ -56,7 +56,11 @@ export class PostForm extends Component { () => console.log('complete') ); - WebSocketService.Instance.listCommunities(); + let listCommunitiesForm: ListCommunitiesForm = { + sort: SortType[SortType.TopAll] + } + + WebSocketService.Instance.listCommunities(listCommunitiesForm); } componentDidMount() { diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts index 660886479..b6139134d 100644 --- a/ui/src/interfaces.ts +++ b/ui/src/interfaces.ts @@ -67,6 +67,12 @@ export interface CommunityResponse { community: Community; } +export interface ListCommunitiesForm { + sort: string; + limit?: number; + auth?: string; +} + export interface ListCommunitiesResponse { op: string; communities: Array; diff --git a/ui/src/services/WebSocketService.ts b/ui/src/services/WebSocketService.ts index b5efd6a7f..99d65adfa 100644 --- a/ui/src/services/WebSocketService.ts +++ b/ui/src/services/WebSocketService.ts @@ -1,5 +1,5 @@ import { wsUri } from '../env'; -import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm } from '../interfaces'; +import { LoginForm, RegisterForm, UserOperation, CommunityForm, PostForm, CommentForm, CommentLikeForm, GetPostsForm, CreatePostLikeForm, FollowCommunityForm, GetUserDetailsForm, ListCommunitiesForm } from '../interfaces'; import { webSocket } from 'rxjs/webSocket'; import { Subject } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; @@ -47,9 +47,9 @@ export class WebSocketService { this.subject.next(this.wsSendWrapper(UserOperation.FollowCommunity, followCommunityForm)); } - public listCommunities() { - let data = {auth: UserService.Instance.auth }; - this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, data)); + public listCommunities(form: ListCommunitiesForm) { + this.setAuth(form, false); + this.subject.next(this.wsSendWrapper(UserOperation.ListCommunities, form)); } public getFollowedCommunities() { From 5cd5c7ccf1649ff158f55897f473168811557b2e Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 10 Apr 2019 11:10:57 -0700 Subject: [PATCH 7/7] Adding docker caching --- Dockerfile | 31 ++++++++++++++++++++++++++++--- ui/fuse.js | 4 ++-- ui/set_version.js | 2 ++ ui/src/version.ts | 2 +- 4 files changed, 33 insertions(+), 6 deletions(-) mode change 100644 => 100755 ui/set_version.js diff --git a/Dockerfile b/Dockerfile index 3e4468dd2..e2803851f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,34 @@ RUN yarn RUN yarn build FROM rust:1.33 as rust -COPY server /app/server + +# create a new empty shell project +WORKDIR /app +RUN USER=root cargo new server WORKDIR /app/server + +# copy over your manifests +COPY server/Cargo.toml server/Cargo.lock ./ + +# this build step will cache your dependencies +RUN mkdir -p ./src/bin \ + && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs +RUN cargo build --release --bin lemmy +RUN ls ./target/release/.fingerprint/ +RUN rm -r ./target/release/.fingerprint/server-* + +# copy your source tree +# RUN rm -rf ./src/ +COPY server/src ./src/ +COPY server/migrations ./migrations/ + +# build for release +RUN cargo build --frozen --release --bin lemmy +RUN mv /app/server/target/release/lemmy /app/lemmy + +# The output image +# FROM debian:stable-slim +# RUN apt-get -y update && apt-get install -y postgresql-client +# COPY --from=rust /app/server/target/release/lemmy /app/lemmy COPY --from=node /app/ui/dist /app/dist -RUN cargo build --release -RUN mv /app/server/target/release/lemmy /app/ EXPOSE 8536 diff --git a/ui/fuse.js b/ui/fuse.js index fe2c7664c..0fdf9a428 100644 --- a/ui/fuse.js +++ b/ui/fuse.js @@ -11,7 +11,7 @@ const transformInferno = require('ts-transform-inferno').default; const transformClasscat = require('ts-transform-classcat').default; let fuse, app; let isProduction = false; -var setVersion = require('./set_version.js').setVersion; +// var setVersion = require('./set_version.js').setVersion; Sparky.task('config', _ => { fuse = new FuseBox({ @@ -42,7 +42,7 @@ Sparky.task('config', _ => { }); app = fuse.bundle('app').instructions('>index.tsx'); }); -Sparky.task('version', _ => setVersion()); +// Sparky.task('version', _ => setVersion()); Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/')); Sparky.task('env', _ => (isProduction = true)); Sparky.task('copy-assets', () => Sparky.src('assets/*.svg').dest('dist/')); diff --git a/ui/set_version.js b/ui/set_version.js old mode 100644 new mode 100755 index bfd640c25..218930852 --- a/ui/set_version.js +++ b/ui/set_version.js @@ -7,3 +7,5 @@ exports.setVersion = function() { let line = `export let version: string = "${revision}";`; fs.writeFileSync("./src/version.ts", line); } + +this.setVersion() diff --git a/ui/src/version.ts b/ui/src/version.ts index 3f710072f..c06ed5dd0 100644 --- a/ui/src/version.ts +++ b/ui/src/version.ts @@ -1 +1 @@ -export let version: string = "v0.0.2-0-gdae6651"; \ No newline at end of file +export let version: string = "v0.0.2-9-g8e5a5d1"; \ No newline at end of file