Browse Source

Merge branch 'main' into feature/frontend-selfhost

feature/frontend-selfhost
Dash8 2 years ago
parent
commit
d502945907
  1. 2
      .dockerignore
  2. 2
      .drone.yml
  3. 19
      README.md
  4. 5
      docker/chapo-dev/docker-compose.yml
  5. 3
      docker/chapo-prod/docker-compose.yml
  6. 82
      docker/dev/Dockerfile
  7. 12
      docker/dev/docker-compose.yml
  8. 2
      docker/iframely.config.local.js
  9. 8
      docker/lemmy.hjson
  10. 5
      docker/prod/Dockerfile
  11. 2
      docker/prod/docker-compose.yml
  12. 2022
      server/Cargo.lock
  13. 88
      server/Cargo.toml
  14. 15
      server/build.rs
  15. 22
      server/config/defaults.hjson
  16. 8
      server/lemmy_api_structs/Cargo.toml
  17. 26
      server/lemmy_db/Cargo.toml
  18. 2
      server/lemmy_db/src/comment_view.rs
  19. 2
      server/lemmy_db/src/post_view.rs
  20. 12
      server/lemmy_rate_limit/Cargo.toml
  21. 24
      server/lemmy_rate_limit/src/lib.rs
  22. 30
      server/lemmy_utils/Cargo.toml
  23. 4
      server/lemmy_utils/src/lib.rs
  24. 5
      server/src/api/site.rs
  25. 107
      server/src/api/user.rs
  26. 3
      server/src/apub/activities.rs
  27. 2
      server/src/apub/activity_queue.rs
  28. 18
      server/src/apub/community.rs
  29. 3
      server/src/apub/extensions/signatures.rs
  30. 2
      server/src/apub/fetcher.rs
  31. 12
      server/src/apub/post.rs
  32. 6
      server/src/apub/user.rs
  33. 36
      server/src/lib.rs
  34. 19
      server/src/main.rs
  35. 3
      server/src/routes/feeds.rs
  36. 22
      server/src/routes/images.rs
  37. 3
      server/src/routes/websocket.rs
  38. 7
      server/src/version.rs
  39. 1
      server/src/websocket/chat_server.rs

2
.dockerignore

@ -4,7 +4,7 @@
# build folders and similar which are not needed for the docker build
ui/node_modules
server/target
# docker/dev/volumes
docker/dev/volumes
# docker/federation/volumes
# docker/federation-test/volumes
#.git # needed to get git sha

2
.drone.yml

@ -30,7 +30,7 @@
path: /cache
- name: build_backend
image: rust:1.47
image: rust:1.54
env:
- CARGO_TARGET_DIR=/var/cache/drone/cargo
- CARGO_HOME=/var/cache/drone/cargo

19
README.md

@ -1,19 +1,16 @@
<div align="center">
<h3>DOES NOT RUN IN RUST 1.48+ DUE TO AN ISSUE IN A DEPENDENCY. USE RUST 1.47</h3>
**Main Branch:** [![Build Status](https://drone.chapo.chat/api/badges/chapo-collective/lemmy-hexbear/status.svg?ref=refs/heads/main)](https://drone.chapo.chat/chapo-collective/lemmy-hexbear)
<div align="center">
<p align="center">
<a href="https://www.hexbear.net" rel="noopener">
<img width=300px height=300px src="https://www.hexbear.net/static/android-chrome-512x512.png"></a>
</p>
**Production:** [![Build Status](https://drone.chapo.chat/api/badges/chapo-collective/lemmy-hexbear/status.svg?ref=refs/heads/prod)](https://drone.chapo.chat/chapo-collective/lemmy-hexbear)
**Main Branch:** [![Build Status](https://drone.chapo.chat/api/badges/hexbear-collective/lemmy-hexbear/status.svg?ref=refs/heads/main)](https://drone.chapo.chat/hexbear-collective/lemmy-hexbear)
**Production:** [![Build Status](https://drone.chapo.chat/api/badges/hexbear-collective/lemmy-hexbear/status.svg?ref=refs/heads/prod)](https://drone.chapo.chat/hexbear-collective/lemmy-hexbear)
[Original Lemmy README](README-lemmy.md)
</div>
<p align="center">
<a href="https://www.chapo.chat" rel="noopener">
<img width=300px height=300px src="ui/public/android-chrome-512x512.png"></a>
</p>
</div>
# Hexbear

5
docker/chapo-dev/docker-compose.yml

@ -68,12 +68,11 @@ services:
restart: always
iframely:
image: dogbin/iframely:latest
container_name: iframely-dev
image: jolt/iframely:v1.6.1
ports:
- "127.0.0.1:8061:80"
volumes:
- ./iframely.config.local.js:/iframely/config.local.js:ro
- ./volumes/iframely/iframely.config.local.js:/iframely/config.local.js:ro
restart: always
purgebot:

3
docker/chapo-prod/docker-compose.yml

@ -81,8 +81,7 @@ services:
restart: always
iframely:
image: dogbin/iframely:latest
container_name: iframely-prod
image: jolt/iframely:v1.6.1
ports:
- "127.0.0.1:8062:80"
volumes:

82
docker/dev/Dockerfile

@ -1,63 +1,54 @@
FROM node:10-jessie as node
ARG RUST_BUILDER=ekidd/rust-musl-builder:latest
WORKDIR /app/ui
FROM $RUST_BUILDER as planner
# Cache deps
COPY ui/package.json ui/yarn.lock ./
RUN yarn install --pure-lockfile
WORKDIR /app
# Build
COPY ui /app/ui
COPY .git/ ./.git/
RUN yarn build
RUN su && rm -rf .git
#install cargo chef
RUN cargo install cargo-chef
FROM ekidd/rust-musl-builder:nightly-2020-05-07 as rust
RUN sudo chown -R rust:rust .
# Cache deps
WORKDIR /app
WORKDIR /app/server
COPY server/ .
# list off needed dependencies
RUN cargo chef prepare --recipe-path recipe.json
FROM ${RUST_BUILDER} as cacher
WORKDIR /app/server
RUN cargo install cargo-chef
COPY --from=planner /app/server/recipe.json recipe.json
RUN sudo chown -R rust:rust .
RUN USER=root cargo new server
RUN cargo chef cook --recipe-path recipe.json
FROM ${RUST_BUILDER} as builder
# Install cargo-build-deps
RUN cargo install --git https://github.com/romac/cargo-build-deps.git
WORKDIR /app/server
RUN mkdir -p lemmy_db/src/ \
lemmy_utils/src/ \
lemmy_api_structs/src/ \
lemmy_rate_limit/src/ \
lemmy
# Copy the cargo tomls
COPY server/Cargo.toml server/Cargo.lock server/build.rs ./
COPY server/lemmy_db/Cargo.toml ./lemmy_db/
COPY server/lemmy_utils/Cargo.toml ./lemmy_utils/
COPY server/lemmy_api_structs/Cargo.toml ./lemmy_api_structs/
COPY server/lemmy_rate_limit/Cargo.toml ./lemmy_rate_limit/
# Cache the deps
RUN cargo build-deps
# Copy the src folders
COPY server/src ./src/
COPY server/lemmy_db/src ./lemmy_db/src/
COPY server/lemmy_utils/src/ ./lemmy_utils/src/
COPY server/lemmy_api_structs/src/ ./lemmy_api_structs/src/
COPY server/lemmy_rate_limit/src/ ./lemmy_rate_limit/src/
COPY server/migrations ./migrations/
COPY .git/ ./.git/
# Build for debug
COPY server/ .
COPY --from=cacher /app/server/target ./target
COPY --from=cacher /home/rust/.cargo /home/rust/.cargo
RUN sudo chown -R rust:rust .
RUN cargo build
FROM ekidd/rust-musl-builder:nightly-2020-05-07 as docs
FROM ${RUST_BUILDER} as docs
WORKDIR /app
COPY docs ./docs
RUN sudo chown -R rust:rust .
RUN mdbook build docs/
FROM alpine:3.12
FROM alpine:3.12 as runner
# Install libpq for postgres
RUN apk add libpq
@ -67,9 +58,8 @@ RUN apk add espeak
# Copy resources
COPY server/config/defaults.hjson /config/defaults.hjson
COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/debug/lemmy_server /app/lemmy
COPY --from=builder /app/server/target/x86_64-unknown-linux-musl/debug/lemmy_server /app/lemmy
COPY --from=docs /app/docs/book/ /app/dist/documentation/
COPY --from=node /app/ui/dist /app/dist
RUN addgroup -g 1000 lemmy
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy

12
docker/dev/docker-compose.yml

@ -3,9 +3,9 @@ version: '3.3'
services:
lemmy:
image: lemmy-dev:latest
image: hexbear-dev:latest
ports:
- "8536:8536"
- 8536:8536
restart: always
environment:
- RUST_LOG=debug
@ -20,7 +20,7 @@ services:
image: postgres:12-alpine
ports:
# use a different port so it doesnt conflict with postgres running on the host
- "5433:5432"
- 5433:5432
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD=password
@ -32,16 +32,16 @@ services:
pictrs:
image: asonix/pictrs:v0.2.5-r0
ports:
- "8537:8080"
- 8537:8080
user: 991:991
volumes:
- ./volumes/pictrs:/mnt
restart: always
iframely:
image: jolt/iframely:v1.4.3
image: jolt/iframely:v1.6.1
ports:
- "8061:80"
- 8061:80
volumes:
- ../iframely.config.local.js:/iframely/config.local.js:ro
restart: always

2
docker/iframely.config.local.js

@ -25,6 +25,8 @@
// request with `redirect loop` error.
MAX_REDIRECTS: 4,
DISABLE_HTTP2: true,
SKIP_OEMBED_RE_LIST: [
// /^https?:\/\/yourdomain\.com\//,
],

8
docker/lemmy.hjson

@ -4,15 +4,15 @@
setup: {
# username for the admin user
admin_username: "lemmy"
admin_username: "hexdev"
# password for the admin user
admin_password: "lemmy"
admin_password: "hexbear"
# name of the site (can be changed later)
site_name: "lemmy-test"
site_name: "hexbear docker dev"
}
# the domain name of your instance (eg "dev.lemmy.ml")
hostname: "my_domain"
hostname: "localhost:4444"
# address where lemmy should listen for incoming requests
bind: "0.0.0.0"
# port where lemmy should listen for incoming requests

5
docker/prod/Dockerfile

@ -1,4 +1,4 @@
ARG RUST_BUILDER_IMAGE=ekidd/rust-musl-builder:1.47.0
ARG RUST_BUILDER_IMAGE=ekidd/rust-musl-builder:latest
ARG FRONTEND_TAG=prod
ARG FRONTEND_REPO=registry.chapo.chat/hexbear-frontend
@ -10,6 +10,7 @@ ARG RUSTRELEASEDIR="release"
# Cache deps
WORKDIR /app
COPY ./.git/ ./.git/
RUN sudo chown -R rust:rust .
RUN USER=root cargo new server
WORKDIR /app/server
@ -26,8 +27,6 @@ RUN find target/$CARGO_BUILD_TARGET/$RUSTRELEASEDIR -type f -name "$(echo "lemmy
COPY server/src ./src/
COPY server/migrations ./migrations/
COPY .git/ ./.git/
# build for release
# workaround for https://github.com/rust-lang/rust/issues/62896
RUN cargo build --frozen --release

2
docker/prod/docker-compose.yml

@ -35,7 +35,7 @@ services:
restart: always
iframely:
image: dogbin/iframely:latest
image: jolt/iframely:v1.6.1
ports:
- "127.0.0.1:8061:80"
volumes:

2022
server/Cargo.lock
File diff suppressed because it is too large
View File

88
server/Cargo.toml

@ -22,56 +22,58 @@ lemmy_db = { path = "./lemmy_db" }
lemmy_api_structs = { path = "./lemmy_api_structs" }
lemmy_rate_limit = { path = "./lemmy_rate_limit" }
activitystreams = "0.7.0-alpha.3"
activitystreams = "0.7.0-alpha.11"
activitystreams-ext = "0.1.0-alpha.2"
actix = "0.10.0-alpha.2"
actix-cors = "0.5.3"
actix-files = "0.3.0-alpha.1"
actix-rt = "1.1.1"
actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
actix-web-actors = "3.0.0-alpha.1"
anyhow = "1.0.32"
async-trait = "0.1.36"
awc = "2.0.0-alpha.2"
background-jobs = " 0.8.0-alpha.2"
base64 = "0.12.1"
bcrypt = "0.8.0"
captcha = "0.0.7"
chrono = { version = "0.4.7", features = ["serde"] }
comrak = "0.7"
config = {version = "0.10.1", default-features = false, features = ["hjson"] }
diesel = { version = "1.4.6", features = ["postgres","chrono","r2d2","64-column-tables","serde_json","uuid"] }
actix = "0.12.0"
# The currently published actix-cors version is incompatible with newer actix-web versions
# let's pull from their git for now and monitor the version published on crates.io
actix-cors = { git = "https://github.com/actix/actix-extras.git" }
actix-files = "0.6.0-beta.6"
actix-rt = "2.2.0"
actix-web = { version = "4.0.0-beta.8", features = ["rustls"] }
actix-web-actors = "4.0.0-beta.6"
anyhow = "1.0.43"
async-trait = "0.1.51"
awc = "3.0.0-beta.7"
background-jobs = "0.9.0"
base64 = "0.13.0"
bcrypt = "0.10.1"
captcha = "0.0.8"
chrono = { version = "0.4.19", features = ["serde"] }
comrak = "0.11.0"
config = { version = "0.11.0", default-features = false, features = ["hjson"] }
diesel = { version = "1.4.7", features = ["postgres","chrono","r2d2","64-column-tables","serde_json","uuidv07"] }
diesel_migrations = "1.4.0"
dotenv = "0.15.0"
enumflags2 = "0.6.4"
env_logger = "0.7.1"
enumflags2 = "0.7.1"
env_logger = "0.9.0"
failure = "0.1.8"
futures = "0.3.5"
futures = "0.3.17"
htmlescape = "0.3.1"
http = "0.2.1"
http-signature-normalization-actix = { version = "0.4.0-alpha.0", default-features = false, features = ["sha-2"] }
itertools = "0.9.0"
jsonwebtoken = "7.0.1"
lazy_static = "1.3.0"
lettre = "0.9.3"
http = "0.2.4"
http-signature-normalization-actix = { version = "0.5.0-beta.7", default-features = false, features = ["sha-2"] }
itertools = "0.10.1"
jsonwebtoken = "7.2.0"
lazy_static = "1.4.0"
lettre = "0.9.6"
lettre_email = "0.9.4"
log = "0.4.0"
openssl = "0.10"
log = "0.4.14"
openssl = "0.10.36"
percent-encoding = "2.1.0"
rand = "0.7.3"
regex = "1.3.5"
reqwest = { version = "0.10", features = ["json"] }
rss = "1.9.0"
serde = { version = "1.0.105", features = ["derive"] }
serde_json = { version = "1.0.52", features = ["preserve_order"]}
sha2 = "0.9"
strum = "0.18.0"
strum_macros = "0.18.0"
thiserror = "1.0.20"
tokio = "0.2.21"
rand = "0.8.4"
regex = "1.5.4"
reqwest = { version = "0.11.4", features = ["json"] }
rss = "1.10.0"
serde = { version = "1.0.130", features = ["derive"] }
serde_json = { version = "1.0.67", features = ["preserve_order"] }
sha2 = "0.9.6"
strum = "0.21.0"
strum_macros = "0.21.1"
thiserror = "1.0.28"
tokio = "1.10.1"
ttl_cache = "0.5.1"
url = { version = "2.1.1", features = ["serde"] }
uuid = { version = "0.6", features = ["serde", "v4"] }
url = { version = "2.2.2", features = ["serde"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] }
[build-dependencies]
vergen = "3"
vergen = "5.1.15"

15
server/build.rs

@ -1,13 +1,14 @@
// build.rs
extern crate vergen;
use vergen::{generate_cargo_keys, ConstantsFlags};
use vergen::{Config, SemverKind, ShaKind, TimestampKind, vergen};
fn main() {
// Setup the flags, toggling off the 'SEMVER_FROM_CARGO_PKG' flag
let mut flags = ConstantsFlags::all();
flags.toggle(ConstantsFlags::SEMVER_FROM_CARGO_PKG);
let mut config = Config::default();
*config.build_mut().kind_mut() = TimestampKind::All;
*config.build_mut().semver_mut() = false;
*config.git_mut().sha_kind_mut() = ShaKind::Short;
*config.git_mut().semver_kind_mut() = SemverKind::Lightweight;
// Generate the 'cargo:' key output
generate_cargo_keys(flags).expect("Unable to generate the cargo keys!");
vergen(config).expect("Unable to generate the cargo keys!");
}

22
server/config/defaults.hjson

@ -1,15 +1,15 @@
{
# # optional: parameters for automatic configuration of new instance (only used at first start)
# setup: {
# # username for the admin user
# admin_username: ""
# # password for the admin user
# admin_password: ""
# # optional: email for the admin user (can be omitted and set later through the website)
# admin_email: ""
# # name of the site (can be changed later)
# site_name: ""
# }
# optional: parameters for automatic configuration of new instance (only used at first start)
setup: {
# username for the admin user
admin_username: "hexdev"
# password for the admin user
admin_password: "hexbear"
# optional: email for the admin user (can be omitted and set later through the website)
admin_email: ""
# name of the site (can be changed later)
site_name: "hexbear development"
}
# settings related to the postgresql database
database: {
# username to connect to postgres

8
server/lemmy_api_structs/Cargo.toml

@ -11,7 +11,7 @@ path = "src/lib.rs"
[dependencies]
lemmy_db = { path = "../lemmy_db" }
chrono = { version = "0.4.7", features = ["serde"] }
serde = { version = "1.0.105", features = ["derive"] }
thiserror = "1.0.20"
uuid = { version = "0.6", features = ["serde", "v4"] }
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.130", features = ["derive"] }
thiserror = "1.0.28"
uuid = { version = "0.8.2", features = ["serde", "v4"] }

26
server/lemmy_db/Cargo.toml

@ -8,16 +8,16 @@ name = "lemmy_db"
path = "src/lib.rs"
[dependencies]
diesel = { version = "1.4.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
chrono = { version = "0.4.7", features = ["serde"] }
serde = { version = "1.0.105", features = ["derive"] }
serde_json = { version = "1.0.52", features = ["preserve_order"]}
strum = "0.18.0"
strum_macros = "0.18.0"
log = "0.4.0"
sha2 = "0.9"
bcrypt = "0.8.0"
url = { version = "2.1.1", features = ["serde"] }
uuid = { version = "0.6", features = ["serde", "v4"] }
lazy_static = "1.3.0"
regex = "1.3.5"
diesel = { version = "1.4.7", features = ["postgres","chrono","r2d2","64-column-tables","serde_json","uuidv07"] }
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.130", features = ["derive"] }
serde_json = { version = "1.0.67", features = ["preserve_order"] }
strum = "0.21.0"
strum_macros = "0.21.1"
log = "0.4.14"
sha2 = "0.9.6"
bcrypt = "0.10.1"
url = { version = "2.2.2", features = ["serde"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] }
lazy_static = "1.4.0"
regex = "1.5.4"

2
server/lemmy_db/src/comment_view.rs

@ -603,6 +603,7 @@ mod tests {
community_id: inserted_community.id,
community_name: inserted_community.name.to_owned(),
community_icon: None,
community_hide_from_all: false,
parent_id: None,
removed: false,
deleted: false,
@ -643,6 +644,7 @@ mod tests {
community_id: inserted_community.id,
community_name: inserted_community.name.to_owned(),
community_icon: None,
community_hide_from_all: false,
parent_id: None,
removed: false,
deleted: false,

2
server/lemmy_db/src/post_view.rs

@ -531,6 +531,7 @@ mod tests {
community_removed: false,
community_deleted: false,
community_nsfw: false,
community_hide_from_all: false,
number_of_comments: 0,
score: 1,
upvotes: 1,
@ -583,6 +584,7 @@ mod tests {
community_removed: false,
community_deleted: false,
community_nsfw: false,
community_hide_from_all: false,
number_of_comments: 0,
score: 1,
upvotes: 1,

12
server/lemmy_rate_limit/Cargo.toml

@ -11,9 +11,9 @@ path = "src/lib.rs"
[dependencies]
lemmy_utils = { path = "../lemmy_utils" }
lemmy_api_structs = { path = "../lemmy_api_structs" }
tokio = "0.2.21"
strum = "0.18.0"
strum_macros = "0.18.0"
futures = "0.3.5"
actix-web = { version = "3.0.0-alpha.3", features = ["rustls"] }
log = "0.4.0"
tokio = "1.10.1"
strum = "0.21.0"
strum_macros = "0.21.1"
futures = "0.3.17"
actix-web = { version = "4.0.0-beta.8", features = ["rustls"] }
log = "0.4.14"

24
server/lemmy_rate_limit/src/lib.rs

@ -7,6 +7,7 @@ pub extern crate tokio;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use futures::future::{ok, Ready};
use lemmy_api_structs::APIError;
use lemmy_utils::{
get_ip,
settings::{RateLimitConfig, Settings},
@ -16,17 +17,14 @@ use rate_limiter::{RateLimitType, RateLimiter};
use std::{
future::Future,
pin::Pin,
sync::Arc,
sync::{Arc, Mutex},
task::{Context, Poll},
};
use tokio::sync::Mutex;
pub mod rate_limiter;
#[derive(Debug, Clone)]
pub struct RateLimit {
// it might be reasonable to use a std::sync::Mutex here, since we don't need to lock this
// across await points
pub rate_limiter: Arc<Mutex<RateLimiter>>,
}
@ -93,7 +91,7 @@ impl RateLimited {
// before
{
let mut limiter = self.rate_limiter.lock().await;
let mut limiter = self.rate_limiter.lock().map_err(|e| APIError::err(format!("Rate limiter mutex poisoned: {:?}", e).as_str()).into())?;
match self.type_ {
RateLimitType::Message => {
@ -169,7 +167,7 @@ impl RateLimited {
// after
{
let mut limiter = self.rate_limiter.lock().await;
let mut limiter = self.rate_limiter.lock().map_err(|e| APIError::err(format!("Rate limiter mutex poisoned: {:?}", e).as_str()).into())?;
if res.is_ok() {
match self.type_ {
RateLimitType::Post => {
@ -199,12 +197,11 @@ impl RateLimited {
}
}
impl<S> Transform<S> for RateLimited
impl<S> Transform<S, ServiceRequest> for RateLimited
where
S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
S::Future: 'static,
{
type Request = S::Request;
type Response = S::Response;
type Error = actix_web::Error;
type InitError = ();
@ -221,21 +218,20 @@ where
type FutResult<T, E> = dyn Future<Output = Result<T, E>>;
impl<S> Service for RateLimitedMiddleware<S>
impl<S> Service<ServiceRequest> for RateLimitedMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
S: Service<ServiceRequest, Response = ServiceResponse, Error = actix_web::Error>,
S::Future: 'static,
{
type Request = S::Request;
type Response = S::Response;
type Error = actix_web::Error;
type Future = Pin<Box<FutResult<Self::Response, Self::Error>>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&mut self, req: S::Request) -> Self::Future {
fn call(&self, req: ServiceRequest) -> Self::Future {
let ip_addr = get_ip(&req.connection_info());
let fut = self

30
server/lemmy_utils/Cargo.toml

@ -10,19 +10,19 @@ path = "src/lib.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.3.5"
config = { version = "0.10.1", default-features = false, features = ["hjson"] }
chrono = { version = "0.4.7", features = ["serde"] }
lettre = "0.9.3"
regex = "1.5.4"
config = { version = "0.11.0", default-features = false, features = ["hjson"] }
chrono = { version = "0.4.19", features = ["serde"] }
lettre = "0.9.6"
lettre_email = "0.9.4"
log = "0.4.0"
itertools = "0.9.0"
rand = "0.7.3"
serde = { version = "1.0.105", features = ["derive"] }
serde_json = { version = "1.0.52", features = ["preserve_order"]}
comrak = "0.7"
lazy_static = "1.3.0"
openssl = "0.10"
url = { version = "2.1.1", features = ["serde"] }
actix-web = "3.0.0-alpha.3"
anyhow = "1.0.32"
log = "0.4.14"
itertools = "0.10.1"
rand = "0.8.4"
serde = { version = "1.0.130", features = ["derive"] }
serde_json = { version = "1.0.67", features = ["preserve_order"] }
comrak = "0.11.0"
lazy_static = "1.4.0"
openssl = "0.10.36"
url = { version = "2.2.2", features = ["serde"] }
actix-web = "4.0.0-beta.8"
anyhow = "1.0.43"

4
server/lemmy_utils/src/lib.rs

@ -140,8 +140,8 @@ pub fn pii_vec_to_str(pii: Vec<&str>) -> String {
[start, combined].concat()
}
pub fn generate_random_string() -> String {
thread_rng().sample_iter(&Alphanumeric).take(30).collect()
pub fn generate_random_string() -> Result<String, anyhow::Error> {
Ok(String::from_utf8(thread_rng().sample_iter(&Alphanumeric).take(30).collect())?)
}
pub fn send_email(

5
server/src/api/site.rs

@ -4,7 +4,7 @@ use actix_web::web::Data;
use anyhow::Context;
use log::{debug, info};
use enumflags2::BitFlags;
use enumflags2::{BitFlags, bitflags};
use lemmy_api_structs::{site::*, user::Register, APIError};
use lemmy_db::{
category::*, comment_view::*, community_view::*, diesel_option_overwrite, moderator::*,
@ -45,7 +45,8 @@ impl Perform for ListCategories {
}
}
#[derive(BitFlags, Copy, Clone, Debug, PartialEq)]
#[bitflags]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u16)]
pub enum ModlogActionFlag {
RemovePost = 0b000000001,

107
server/src/api/user.rs

@ -244,48 +244,51 @@ impl Perform for Register {
return Err(APIError::err("admin_already_created").into());
}
// Make sure site has open registration
if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
let site: SiteView = site;
if !site.open_registration {
return Err(APIError::err("registration_closed").into());
// If its not the admin, check other information about registration
if !data.admin {
// Make sure site has open registration
if let Ok(site) = blocking(context.pool(), move |conn| SiteView::read(conn)).await? {
let site: SiteView = site;
if !site.open_registration {
return Err(APIError::err("registration_closed").into());
}
}
}
// Make sure passwords match
if data.password != data.password_verify {
return Err(APIError::err("passwords_dont_match").into());
}
// Make sure passwords match
if data.password != data.password_verify {
return Err(APIError::err("passwords_dont_match").into());
}
// If its not the admin, check the captcha
if !data.admin && Settings::get().captcha.enabled {
match Settings::get().captcha.provider.as_str() {
"hcaptcha" => {
if let Some(hcaptcha_id) = data.hcaptcha_id.clone() {
if let Err(hcaptcha_error) = hcaptcha_verify(hcaptcha_id).await {
error!("hCaptcha failed: {:?}", hcaptcha_error);
return Err(APIError::err("captcha_failed").into());
//Check the captcha if it's enabled
if Settings::get().captcha.enabled {
match Settings::get().captcha.provider.as_str() {
"hcaptcha" => {
if let Some(hcaptcha_id) = data.hcaptcha_id.clone() {
if let Err(hcaptcha_error) = hcaptcha_verify(hcaptcha_id).await {
error!("hCaptcha failed: {:?}", hcaptcha_error);
return Err(APIError::err("captcha_failed").into());
}
} else {
return Err(APIError::err("missing_hcaptcha_id").into());
}
} else {
return Err(APIError::err("missing_hcaptcha_id").into());
}
}
_ => {
let check = context
.chat_server()
.send(CheckCaptcha {
uuid: data
.captcha_uuid
.to_owned()
.unwrap_or_else(|| "".to_string()),
answer: data
.captcha_answer
.to_owned()
.unwrap_or_else(|| "".to_string()),
})
.await?;
if !check {
return Err(APIError::err("captcha_incorrect").into());
_ => {
let check = context
.chat_server()
.send(CheckCaptcha {
uuid: data
.captcha_uuid
.to_owned()
.unwrap_or_else(|| "".to_string()),
answer: data
.captcha_answer
.to_owned()
.unwrap_or_else(|| "".to_string()),
})
.await?;
if !check {
return Err(APIError::err("captcha_incorrect").into());
}
}
}
}
@ -313,7 +316,7 @@ impl Perform for Register {
let user_form = UserForm {
name: data.username.to_owned(),
email: Some(email.to_owned()),
admin: false,
admin: data.admin, //only executes up to this point if there are no other admins.
matrix_user_id: None,
avatar: None,
banner: None,
@ -387,10 +390,28 @@ impl Perform for Register {
icon: None,
banner: None,
};
blocking(context.pool(), move |conn| {
let community = blocking(context.pool(), move |conn| {
Community::create(conn, &community_form)
})
.await??
.await??;
// Initialize community settings
let community_settings_form = CommunitySettingsForm {
id: community.id,
read_only: false,
private: false,
post_links: true,
comment_images: 1,
allow_as_default: true,
hide_from_all: false,
};
let _inserted_settings = blocking(context.pool(), move |conn| {
CommunitySettings::create(conn, &community_settings_form)
})
.await??;
community
}
};
@ -406,9 +427,9 @@ impl Perform for Register {
};*/
//get comms that are both admin-selected and enabled allow_as_default, then subscribe users to all of them
//if the read site throws an error, we may be registering before the site is created. just pass over no defaults
let default_communities = blocking(context.pool(), move |conn| SiteView::read(conn))
.await??
.autosubscribe_comms;
.await?.map(|s| s.autosubscribe_comms).unwrap_or(Vec::new());
let optin_communities = blocking(context.pool(), move |conn| {
CommunitySettings::list_allowed_as_default(conn)
})
@ -1221,7 +1242,7 @@ impl Perform for PasswordReset {
};
// Generate a random token
let token = generate_random_string();
let token = generate_random_string()?;
// Insert the row
let token2 = token.clone();

3
server/src/apub/activities.rs

@ -6,9 +6,10 @@ use activitystreams::{
base::{Extends, ExtendsExt},
object::AsObject,
};
use core::fmt::Debug;
use lemmy_db::{community::Community, user::User_};
use lemmy_utils::{get_apub_protocol_string, settings::Settings, LemmyError};
use serde::{export::fmt::Debug, Serialize};
use serde::{Serialize};
use url::{ParseError, Url};
use uuid::Uuid;

2
server/src/apub/activity_queue.rs

@ -71,7 +71,7 @@ impl ActixJob for SendActivityTask {
let request = state
.client
.post(to_url.as_str())
.header("Content-Type", "application/json");
.insert_header(("Content-Type", "application/json"));
// TODO: i believe we have to do the signing in here because it is only valid for a few seconds
let signed = sign(

18
server/src/apub/community.rs

@ -10,18 +10,10 @@ use crate::{
},
blocking, DbPool, LemmyContext,
};
use activitystreams::{
activity::{
use activitystreams::{activity::{
kind::{AcceptType, AnnounceType, DeleteType, LikeType, RemoveType, UndoType},
Accept, Announce, Delete, Follow, Remove, Undo,
},
actor::{kind::GroupType, ApActor, Endpoints, Group},
base::{AnyBase, BaseExt},
collection::{OrderedCollection, UnorderedCollection},
object::{Image, Tombstone},
prelude::*,
public,
};
}, actor::{kind::GroupType, ApActor, Endpoints, Group}, base::{AnyBase, BaseExt}, collection::{OrderedCollection, UnorderedCollection}, object::{Image, Tombstone}, prelude::*, public};
use activitystreams_ext::Ext2;
use actix_web::{body::Body, web, HttpResponse};
use anyhow::Context;
@ -344,9 +336,9 @@ impl FromApub for CommunityForm {
let description = group
.inner
.content()
.map(|s| s.as_single_xsd_string())
.flatten()
.map(|s| s.to_string());
.map(|s| s.as_single_xsd_string()
.map(|str| str.to_string()))
.flatten();
check_slurs(&name)?;
check_slurs(&title)?;
check_slurs_opt(&description)?;

3
server/src/apub/extensions/signatures.rs

@ -1,7 +1,8 @@
use crate::apub::ActorType;
use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension;
use actix_web::{client::ClientRequest, HttpRequest};
use actix_web::HttpRequest;
use awc::ClientRequest;
use anyhow::{anyhow, Context};
use http_signature_normalization_actix::{
digest::{DigestClient, SignExt},

2
server/src/apub/fetcher.rs

@ -69,8 +69,8 @@ where
}
/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
#[serde(untagged)]
#[derive(serde::Deserialize, Debug)]
#[serde(untagged)]
pub enum SearchAcceptedObjects {
Person(Box<PersonExt>),
Group(Box<GroupExt>),

12
server/src/apub/post.rs

@ -152,14 +152,14 @@ fn extract_embed_from_apub(
.map(|s| s.to_string());
let description = preview_page
.summary()
.map(|s| s.as_single_xsd_string())
.flatten()
.map(|s| s.to_string());
.map(|s| s.as_single_xsd_string()
.map(|str| str.to_string()))
.flatten();
let html = preview_page
.content()
.map(|c| c.as_single_xsd_string())
.flatten()
.map(|s| s.to_string());
.map(|c| c.as_single_xsd_string()
.map(|str| str.to_string()))
.flatten();
Ok(EmbedType {
title,
description,

6
server/src/apub/user.rs

@ -233,9 +233,9 @@ impl FromApub for UserForm {
let bio = person
.inner
.summary()
.map(|s| s.as_single_xsd_string())
.flatten()
.map(|s| s.to_string());
.map(|s| s.as_single_xsd_string()
.map(|str| str.to_string()))
.flatten();
check_slurs(&name)?;
check_slurs_opt(&preferred_username)?;
check_slurs_opt(&bio)?;

36
server/src/lib.rs

@ -102,14 +102,34 @@ impl LemmyContext {
#[derive(Deserialize, Debug)]
pub struct IframelyResponse {
title: Option<String>,
html: Option<String>,
links: Option<IframelyLinks>,
meta: Option<IframelyMeta>
}
#[derive(Deserialize, Debug)]
pub struct IframelyMeta {
description: Option<String>,
thumbnail_url: Option<String>,
title: Option<String>,
site: Option<String>
}
#[derive(Deserialize, Debug)]
pub struct IframelyLink {
href: Option<String>,
html: Option<String>,
#[serde(rename = "type")]
content_type: Option<String>
}
#[derive(Deserialize, Debug)]
pub struct IframelyLinks {
icon: Option<Vec<IframelyLink>>,
thumbnail: Option<Vec<IframelyLink>>,
}
pub async fn fetch_iframely(client: &Client, url: &str) -> Result<IframelyResponse, LemmyError> {
let fetch_url = format!("http://iframely/oembed?url={}", url);
let fetch_url = format!("http://iframely/iframely?url={}", url);
let response = retry(|| client.get(&fetch_url).send()).await?;
@ -168,7 +188,11 @@ async fn fetch_iframely_and_pictrs_data(
// Fetch iframely data
let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) =
match fetch_iframely(client, url).await {
Ok(res) => (res.title, res.description, res.thumbnail_url, res.html),
Ok(res) => {
let meta = res.meta.map(|m| (m.title, m.description)).unwrap_or((None, None));
let thumbnail = res.links.map(|l| l.thumbnail.map(|t| t[0].href.clone()).flatten()).flatten();
(meta.0, meta.1, thumbnail, res.html)
},
Err(e) => {
error!("iframely err: {}", e);
(None, None, None, None)
@ -244,7 +268,7 @@ where
let res = (f)(&conn);
Ok(res) as Result<_, LemmyError>
})
.await?;
.await??;
Ok(res)
}
@ -319,7 +343,7 @@ mod tests {
#[test]
fn test_image() {
actix_rt::System::new("tset_image").block_on(async move {
actix_rt::System::new().block_on(async move {
let client = reqwest::Client::default();
assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok());
assert!(is_image_content_type(&client,

19
server/src/main.rs

@ -5,19 +5,15 @@ pub extern crate lazy_static;
use actix::prelude::*;
use actix_cors::Cors;
use actix_web::{
body::Body,
dev::{Service, ServiceRequest, ServiceResponse},
http::{
use actix_web::{*, body::Body, dev::{Service, ServiceRequest, ServiceResponse}, http::{
header::{CACHE_CONTROL, CONTENT_TYPE},
HeaderValue,
},
*,
};
}, web::Data};
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use futures::Future;
use lemmy_db::get_database_url_from_env;
use lemmy_rate_limit::{rate_limiter::RateLimiter, RateLimit};
use lemmy_server::{
@ -28,8 +24,7 @@ use lemmy_utils::{
settings::Settings, LemmyError, CACHE_CONTROL_APPLICATION_REGEX, CACHE_CONTROL_IMAGE_REGEX,
};
use reqwest::Client;
use std::sync::Arc;
use tokio::sync::Mutex;
use std::sync::{Arc, Mutex};
lazy_static! {
// static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 365 * 24 * 60 * 60);
@ -108,7 +103,7 @@ async fn main() -> Result<(), LemmyError> {
.wrap_fn(add_cache_headers)
.wrap(cors)
.wrap(middleware::Logger::default())
.data(context)
.app_data(Data::new(context))
// The routes
.configure(|cfg| api::config(cfg, &rate_limiter))
.configure(federation::config)
@ -126,10 +121,10 @@ async fn main() -> Result<(), LemmyError> {
fn add_cache_headers<S>(
req: ServiceRequest,
srv: &mut S,
srv: &S,
) -> impl Future<Output = Result<ServiceResponse, Error>>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>,
S: Service<ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
{
let fut = srv.call(req);
async move {

3
server/src/routes/feeds.rs

@ -79,10 +79,11 @@ fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String
}
async fn get_feed(
web::Path((r_type, name)): web::Path<(String, String)>,
path: web::Path<(String, String)>,
info: web::Query<Params>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
let (r_type, name) = path.into_inner();
let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
let request_type = match r_type.as_str() {

22
server/src/routes/images.rs

@ -1,6 +1,6 @@
use crate::api::claims::Claims;
use actix::clock::Duration;
use actix_web::{body::BodyStream, http::StatusCode, *};
use std::time::Duration;
use actix_web::{*, body::BodyStream, http::StatusCode, web::Data};
use awc::Client;
use lemmy_rate_limit::RateLimit;
use lemmy_utils::settings::Settings;
@ -13,7 +13,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
.finish();
cfg
.data(client)
.app_data(Data::new(client))
.service(
web::resource("/pictrs/image")
.wrap(rate_limit.image())
@ -58,12 +58,12 @@ async fn upload(
client.request_from(format!("{}/image", Settings::get().pictrs_url), req.head());
if let Some(addr) = req.head().peer_addr {
client_req = client_req.header("X-Forwarded-For", addr.to_string())
client_req = client_req.append_header(("X-Forwarded-For", addr.to_string()))
};
let mut res = client_req.send_stream(body).await?;
let mut res = client_req.send_stream(body).await.map_err(|e| error::ErrorBadRequest(e))?;
let images = res.json::<Images>().await?;
let images = res.json::<Images>().await.map_err(|e| error::ErrorBadRequest(e))?;
Ok(HttpResponse::build(res.status()).json(images))
}
@ -107,10 +107,10 @@ async fn image(
let mut client_req = client.request_from(url, req.head());
if let Some(addr) = req.head().peer_addr {
client_req = client_req.header("X-Forwarded-For", addr.to_string())
client_req = client_req.append_header(("X-Forwarded-For", addr.to_string()))
};
let res = client_req.no_decompress().send().await?;
let res = client_req.no_decompress().send().await.map_err(|e| error::ErrorBadRequest(e))?;
if res.status() == StatusCode::NOT_FOUND {
return Ok(HttpResponse::NotFound().finish());
@ -119,7 +119,7 @@ async fn image(
let mut client_res = HttpResponse::build(res.status());
for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
client_res.header(name.clone(), value.clone());
client_res.append_header((name.clone(), value.clone()));
}
Ok(client_res.body(BodyStream::new(res)))
@ -142,10 +142,10 @@ async fn delete(
let mut client_req = client.request_from(url, req.head());
if let Some(addr) = req.head().peer_addr {
client_req = client_req.header("X-Forwarded-For", addr.to_string())
client_req = client_req.append_header(("X-Forwarded-For", addr.to_string()))
};
let res = client_req.no_decompress().send().await?;
let res = client_req.no_decompress().send().await.map_err(|e| error::ErrorBadRequest(e))?;
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res)))
}

3
server/src/routes/websocket.rs

@ -8,6 +8,7 @@ use crate::{
use actix::prelude::*;
use actix_web::*;
use actix_web_actors::ws;
use core::ops::Deref;
use lemmy_utils::get_ip;
use log::{debug, error, info};
use std::time::{Duration, Instant};
@ -28,7 +29,7 @@ pub async fn chat_route(
cs_addr: context.chat_server().to_owned(),
id: 0,
hb: Instant::now(),
ip: get_ip(&req.connection_info()),
ip: get_ip(req.connection_info().deref()),
},
&req,
stream,

7
server/src/version.rs

@ -1,6 +1,5 @@
pub const VERSION: &str = "v0.7.57";
pub const GIT_SHA: &str = env!("VERGEN_SHA");
pub const GIT_SHA_SHORT: &str = env!("VERGEN_SHA_SHORT");
pub const SEMVER_LIGHTWEIGHT: &str = env!("VERGEN_SEMVER_LIGHTWEIGHT");
pub const VERSION: &str = "v0.7.60.hex";
pub const GIT_SHA_SHORT: &str = env!("VERGEN_GIT_SHA_SHORT");
pub const SEMVER_LIGHTWEIGHT: &str = env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT");
pub const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP");
pub const BUILD_DATE: &str = env!("VERGEN_BUILD_DATE");

1
server/src/websocket/chat_server.rs

@ -14,6 +14,7 @@ use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
use futures::Future;
use lemmy_api_structs::{
comment::*, community::*, community_settings::*, post::*, post_hexbear::FeaturePost, report::*,
site::*, user::*, APIError,

Loading…
Cancel
Save