mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-22 20:31:19 +00:00
Adding visual captchas for register and login. (#1027)
* Adding visual captchas for register and login. * Adding audio wav file for Captcha using espeak. * Lots of captcha fixes. - Removed login captchas. - Added settings to disable captchas, and change difficulty. - Captchas can only be checked / used once, front end gives a new one on failure. - Added front end button for regenerating captcha. - Added a disabled / pause button audio playing. * Some more fixes.
This commit is contained in:
parent
5dd0efb280
commit
49bd28e2d4
20 changed files with 670 additions and 63 deletions
3
docker/dev/Dockerfile
vendored
3
docker/dev/Dockerfile
vendored
|
@ -41,6 +41,9 @@ FROM alpine:3.12
|
||||||
# Install libpq for postgres
|
# Install libpq for postgres
|
||||||
RUN apk add libpq
|
RUN apk add libpq
|
||||||
|
|
||||||
|
# Install Espeak for captchas
|
||||||
|
RUN apk add espeak
|
||||||
|
|
||||||
# Copy resources
|
# Copy resources
|
||||||
COPY server/config/defaults.hjson /config/defaults.hjson
|
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=rust /app/server/target/x86_64-unknown-linux-musl/debug/lemmy_server /app/lemmy
|
||||||
|
|
4
docker/prod/Dockerfile
vendored
4
docker/prod/Dockerfile
vendored
|
@ -50,6 +50,10 @@ FROM alpine:3.12 as lemmy
|
||||||
|
|
||||||
# Install libpq for postgres
|
# Install libpq for postgres
|
||||||
RUN apk add libpq
|
RUN apk add libpq
|
||||||
|
|
||||||
|
# Install Espeak for captchas
|
||||||
|
RUN apk add espeak
|
||||||
|
|
||||||
RUN addgroup -g 1000 lemmy
|
RUN addgroup -g 1000 lemmy
|
||||||
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy
|
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy
|
||||||
|
|
||||||
|
|
32
docs/src/contributing_websocket_http_api.md
vendored
32
docs/src/contributing_websocket_http_api.md
vendored
|
@ -390,7 +390,9 @@ Only the first user will be able to be the admin.
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
password: String,
|
password: String,
|
||||||
password_verify: String,
|
password_verify: String,
|
||||||
admin: bool
|
admin: bool,
|
||||||
|
captcha_uuid: Option<String>, // Only checked if these are enabled in the server
|
||||||
|
captcha_answer: Option<String>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -408,6 +410,34 @@ Only the first user will be able to be the admin.
|
||||||
|
|
||||||
`POST /user/register`
|
`POST /user/register`
|
||||||
|
|
||||||
|
#### Get Captcha
|
||||||
|
|
||||||
|
These expire after 10 minutes.
|
||||||
|
|
||||||
|
##### Request
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
op: "GetCaptcha",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
##### Response
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
op: "GetCaptcha",
|
||||||
|
data: {
|
||||||
|
ok?: { // Will be undefined if captchas are disabled
|
||||||
|
png: String, // A Base64 encoded png
|
||||||
|
wav: Option<String>, // A Base64 encoded wav audio file
|
||||||
|
uuid: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### HTTP
|
||||||
|
|
||||||
|
`GET /user/get_captcha`
|
||||||
|
|
||||||
#### Get User Details
|
#### Get User Details
|
||||||
##### Request
|
##### Request
|
||||||
```rust
|
```rust
|
||||||
|
|
295
server/Cargo.lock
generated
vendored
295
server/Cargo.lock
generated
vendored
|
@ -31,7 +31,7 @@ checksum = "a9028932f36d45df020c92317ccb879ab77d8f066f57ff143dd5bee93ba3de0d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix_derive",
|
"actix_derive",
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
@ -54,7 +54,7 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
|
checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
@ -94,7 +94,7 @@ dependencies = [
|
||||||
"actix-http",
|
"actix-http",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -120,7 +120,7 @@ dependencies = [
|
||||||
"actix-tls",
|
"actix-tls",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"base64 0.12.3",
|
"base64 0.12.3",
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"brotli2",
|
"brotli2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cookie",
|
"cookie",
|
||||||
|
@ -280,7 +280,7 @@ dependencies = [
|
||||||
"actix-codec",
|
"actix-codec",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"either",
|
"either",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -382,6 +382,12 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler32"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.13"
|
version = "0.7.13"
|
||||||
|
@ -496,6 +502,15 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
|
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -539,6 +554,12 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -642,6 +663,12 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
@ -663,6 +690,26 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "c_vec"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8a318911dce53b5f1ca6539c44f5342c632269f0fa7ea3e35f32458c27a7c30"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "captcha"
|
||||||
|
version = "0.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d060a3be43adb2fe89d3448e9a193149806139b1ce99281865fcab7aeaf04ed"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.5.2",
|
||||||
|
"image",
|
||||||
|
"lodepng",
|
||||||
|
"rand 0.3.23",
|
||||||
|
"serde_json",
|
||||||
|
"time 0.1.43",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.58"
|
version = "1.0.58"
|
||||||
|
@ -695,7 +742,7 @@ checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"strsim 0.8.0",
|
"strsim 0.8.0",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
@ -708,7 +755,7 @@ version = "0.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -717,9 +764,15 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "comrak"
|
name = "comrak"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -806,6 +859,43 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"maybe-uninit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0",
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"lazy_static",
|
||||||
|
"maybe-uninit",
|
||||||
|
"memoffset",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-queue"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"maybe-uninit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -852,6 +942,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deflate"
|
||||||
|
version = "0.7.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_builder"
|
name = "derive_builder"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -894,7 +994,7 @@ version = "1.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
|
checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel_derives",
|
"diesel_derives",
|
||||||
|
@ -1072,6 +1172,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum_primitive"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits 0.1.43",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1167,7 +1276,7 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"fuchsia-zircon-sys",
|
"fuchsia-zircon-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1311,6 +1420,16 @@ dependencies = [
|
||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gif"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f"
|
||||||
|
dependencies = [
|
||||||
|
"color_quant",
|
||||||
|
"lzw",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
|
@ -1455,6 +1574,23 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c3f4f5ea213ed9899eca760a8a14091d4b82d33e27cf8ced336ff730e9f6da8"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"enum_primitive",
|
||||||
|
"gif",
|
||||||
|
"jpeg-decoder",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits 0.1.43",
|
||||||
|
"png",
|
||||||
|
"scoped_threadpool",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -1465,6 +1601,12 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inflate"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1238524675af3938a7c74980899535854b88ba07907bb1c944abe5b8fc437e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -1507,6 +1649,16 @@ version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jpeg-decoder"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.42"
|
version = "0.3.42"
|
||||||
|
@ -1585,6 +1737,7 @@ dependencies = [
|
||||||
"awc",
|
"awc",
|
||||||
"base64 0.12.3",
|
"base64 0.12.3",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
|
"captcha",
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
|
@ -1673,7 +1826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
|
checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"ryu",
|
"ryu",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
|
@ -1719,6 +1872,18 @@ dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lodepng"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ac1dfdf85b7d5dea61a620e12c051a72078189366a0b3c0ab331e30847def2f"
|
||||||
|
dependencies = [
|
||||||
|
"c_vec",
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -1737,6 +1902,12 @@ dependencies = [
|
||||||
"linked-hash-map 0.5.3",
|
"linked-hash-map 0.5.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lzw"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maplit"
|
name = "maplit"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -1755,12 +1926,27 @@ version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-uninit"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.3"
|
version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "migrations_internals"
|
name = "migrations_internals"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -1920,6 +2106,27 @@ dependencies = [
|
||||||
"num-traits 0.2.12",
|
"num-traits 0.2.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits 0.2.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.1.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits 0.2.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
|
@ -1978,7 +2185,7 @@ version = "0.10.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
|
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -2153,6 +2360,18 @@ version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48f397b84083c2753ba53c7b56ad023edb94512b2885ffe227c66ff7edb61868"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 0.7.0",
|
||||||
|
"deflate",
|
||||||
|
"inflate",
|
||||||
|
"num-iter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -2225,6 +2444,16 @@ dependencies = [
|
||||||
"scheduled-thread-pool",
|
"scheduled-thread-pool",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.3.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand 0.4.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -2385,6 +2614,31 @@ dependencies = [
|
||||||
"rand_core 0.3.1",
|
"rand_core 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0",
|
||||||
|
"crossbeam-deque",
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-queue",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"lazy_static",
|
||||||
|
"num_cpus",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rdrand"
|
name = "rdrand"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -2437,6 +2691,15 @@ dependencies = [
|
||||||
"quick-error",
|
"quick-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90ef54b45ae131327a88597e2463fee4098ad6c88ba7b6af4b3987db8aad4098"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.15"
|
version = "0.16.15"
|
||||||
|
@ -2521,6 +2784,12 @@ dependencies = [
|
||||||
"parking_lot 0.11.0",
|
"parking_lot 0.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped_threadpool"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2543,7 +2812,7 @@ version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
|
checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 1.2.1",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
1
server/Cargo.toml
vendored
1
server/Cargo.toml
vendored
|
@ -51,3 +51,4 @@ itertools = "0.9.0"
|
||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
sha2 = "0.9"
|
sha2 = "0.9"
|
||||||
async-trait = "0.1.36"
|
async-trait = "0.1.36"
|
||||||
|
captcha = "0.0.7"
|
||||||
|
|
4
server/config/defaults.hjson
vendored
4
server/config/defaults.hjson
vendored
|
@ -59,6 +59,10 @@
|
||||||
# comma seperated list of instances with which federation is allowed
|
# comma seperated list of instances with which federation is allowed
|
||||||
allowed_instances: ""
|
allowed_instances: ""
|
||||||
}
|
}
|
||||||
|
captcha: {
|
||||||
|
enabled: true
|
||||||
|
difficulty: medium # Can be easy, medium, or hard
|
||||||
|
}
|
||||||
# # email sending configuration
|
# # email sending configuration
|
||||||
# email: {
|
# email: {
|
||||||
# # hostname and port of the smtp server
|
# # hostname and port of the smtp server
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub struct Settings {
|
||||||
pub rate_limit: RateLimitConfig,
|
pub rate_limit: RateLimitConfig,
|
||||||
pub email: Option<EmailConfig>,
|
pub email: Option<EmailConfig>,
|
||||||
pub federation: Federation,
|
pub federation: Federation,
|
||||||
|
pub captcha: CaptchaConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
@ -46,6 +47,12 @@ pub struct EmailConfig {
|
||||||
pub use_tls: bool,
|
pub use_tls: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct CaptchaConfig {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub difficulty: String, // easy, medium, or hard
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
pub user: String,
|
pub user: String,
|
||||||
|
|
|
@ -370,6 +370,8 @@ impl Perform for Oper<GetSite> {
|
||||||
password_verify: setup.admin_password.to_owned(),
|
password_verify: setup.admin_password.to_owned(),
|
||||||
admin: true,
|
admin: true,
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
|
captcha_uuid: None,
|
||||||
|
captcha_answer: None,
|
||||||
};
|
};
|
||||||
let login_response = Oper::new(register, self.client.clone())
|
let login_response = Oper::new(register, self.client.clone())
|
||||||
.perform(pool, websocket_info.clone())
|
.perform(pool, websocket_info.clone())
|
||||||
|
|
|
@ -2,8 +2,9 @@ use crate::{
|
||||||
api::{claims::Claims, is_admin, APIError, Oper, Perform},
|
api::{claims::Claims, is_admin, APIError, Oper, Perform},
|
||||||
apub::ApubObjectType,
|
apub::ApubObjectType,
|
||||||
blocking,
|
blocking,
|
||||||
|
captcha_espeak_wav_base64,
|
||||||
websocket::{
|
websocket::{
|
||||||
server::{JoinUserRoom, SendAllMessage, SendUserRoomMessage},
|
server::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage},
|
||||||
UserOperation,
|
UserOperation,
|
||||||
WebsocketInfo,
|
WebsocketInfo,
|
||||||
},
|
},
|
||||||
|
@ -11,6 +12,8 @@ use crate::{
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use bcrypt::verify;
|
use bcrypt::verify;
|
||||||
|
use captcha::{gen, Difficulty};
|
||||||
|
use chrono::Duration;
|
||||||
use lemmy_db::{
|
use lemmy_db::{
|
||||||
comment::*,
|
comment::*,
|
||||||
comment_view::*,
|
comment_view::*,
|
||||||
|
@ -66,6 +69,23 @@ pub struct Register {
|
||||||
pub password_verify: String,
|
pub password_verify: String,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
pub show_nsfw: bool,
|
pub show_nsfw: bool,
|
||||||
|
pub captcha_uuid: Option<String>,
|
||||||
|
pub captcha_answer: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GetCaptcha {}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GetCaptchaResponse {
|
||||||
|
ok: Option<CaptchaResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CaptchaResponse {
|
||||||
|
png: String, // A Base64 encoded png
|
||||||
|
wav: Option<String>, // A Base64 encoded wav audio
|
||||||
|
uuid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -303,7 +323,7 @@ impl Perform for Oper<Register> {
|
||||||
async fn perform(
|
async fn perform(
|
||||||
&self,
|
&self,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
_websocket_info: Option<WebsocketInfo>,
|
websocket_info: Option<WebsocketInfo>,
|
||||||
) -> Result<LoginResponse, LemmyError> {
|
) -> Result<LoginResponse, LemmyError> {
|
||||||
let data: &Register = &self.data;
|
let data: &Register = &self.data;
|
||||||
|
|
||||||
|
@ -320,6 +340,31 @@ impl Perform for Oper<Register> {
|
||||||
return Err(APIError::err("passwords_dont_match").into());
|
return Err(APIError::err("passwords_dont_match").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If its not the admin, check the captcha
|
||||||
|
if !data.admin && Settings::get().captcha.enabled {
|
||||||
|
match websocket_info {
|
||||||
|
Some(ws) => {
|
||||||
|
let check = ws
|
||||||
|
.chatserver
|
||||||
|
.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return Err(APIError::err("captcha_incorrect").into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(slurs) = slur_check(&data.username) {
|
if let Err(slurs) = slur_check(&data.username) {
|
||||||
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
|
return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
|
||||||
}
|
}
|
||||||
|
@ -439,6 +484,54 @@ impl Perform for Oper<Register> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for Oper<GetCaptcha> {
|
||||||
|
type Response = GetCaptchaResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
_pool: &DbPool,
|
||||||
|
websocket_info: Option<WebsocketInfo>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let captcha_settings = Settings::get().captcha;
|
||||||
|
|
||||||
|
if !captcha_settings.enabled {
|
||||||
|
return Ok(GetCaptchaResponse { ok: None });
|
||||||
|
}
|
||||||
|
|
||||||
|
let captcha = match captcha_settings.difficulty.as_str() {
|
||||||
|
"easy" => gen(Difficulty::Easy),
|
||||||
|
"medium" => gen(Difficulty::Medium),
|
||||||
|
"hard" => gen(Difficulty::Hard),
|
||||||
|
_ => gen(Difficulty::Medium),
|
||||||
|
};
|
||||||
|
|
||||||
|
let answer = captcha.chars_as_string();
|
||||||
|
|
||||||
|
let png_byte_array = captcha.as_png().expect("failed to generate captcha");
|
||||||
|
|
||||||
|
let png = base64::encode(png_byte_array);
|
||||||
|
|
||||||
|
let uuid = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
let wav = captcha_espeak_wav_base64(&answer).ok();
|
||||||
|
|
||||||
|
let captcha_item = CaptchaItem {
|
||||||
|
answer,
|
||||||
|
uuid: uuid.to_owned(),
|
||||||
|
expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ws) = websocket_info {
|
||||||
|
ws.chatserver.do_send(captcha_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GetCaptchaResponse {
|
||||||
|
ok: Some(CaptchaResponse { png, uuid, wav }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl Perform for Oper<SaveUserSettings> {
|
impl Perform for Oper<SaveUserSettings> {
|
||||||
type Response = LoginResponse;
|
type Response = LoginResponse;
|
||||||
|
|
|
@ -7,7 +7,9 @@ pub extern crate lazy_static;
|
||||||
pub extern crate failure;
|
pub extern crate failure;
|
||||||
pub extern crate actix;
|
pub extern crate actix;
|
||||||
pub extern crate actix_web;
|
pub extern crate actix_web;
|
||||||
|
pub extern crate base64;
|
||||||
pub extern crate bcrypt;
|
pub extern crate bcrypt;
|
||||||
|
pub extern crate captcha;
|
||||||
pub extern crate chrono;
|
pub extern crate chrono;
|
||||||
pub extern crate diesel;
|
pub extern crate diesel;
|
||||||
pub extern crate dotenv;
|
pub extern crate dotenv;
|
||||||
|
@ -35,6 +37,7 @@ use lemmy_utils::{get_apub_protocol_string, settings::Settings};
|
||||||
use log::error;
|
use log::error;
|
||||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
|
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
|
||||||
pub type ConnectionId = usize;
|
pub type ConnectionId = usize;
|
||||||
|
@ -224,9 +227,56 @@ where
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
|
||||||
|
let mut built_text = String::new();
|
||||||
|
|
||||||
|
// Building proper speech text for espeak
|
||||||
|
for mut c in captcha.chars() {
|
||||||
|
let new_str = if c.is_alphabetic() {
|
||||||
|
if c.is_lowercase() {
|
||||||
|
c.make_ascii_uppercase();
|
||||||
|
format!("lower case {} ... ", c)
|
||||||
|
} else {
|
||||||
|
c.make_ascii_uppercase();
|
||||||
|
format!("capital {} ... ", c)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{} ...", c)
|
||||||
|
};
|
||||||
|
|
||||||
|
built_text.push_str(&new_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
espeak_wav_base64(&built_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
|
||||||
|
// Make a temp file path
|
||||||
|
let uuid = uuid::Uuid::new_v4().to_string();
|
||||||
|
let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
|
||||||
|
|
||||||
|
// Write the wav file
|
||||||
|
Command::new("espeak")
|
||||||
|
.arg("-w")
|
||||||
|
.arg(&file_path)
|
||||||
|
.arg(text)
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
// Read the wav file bytes
|
||||||
|
let bytes = std::fs::read(&file_path)?;
|
||||||
|
|
||||||
|
// Delete the file
|
||||||
|
std::fs::remove_file(file_path)?;
|
||||||
|
|
||||||
|
// Convert to base64
|
||||||
|
let base64 = base64::encode(bytes);
|
||||||
|
|
||||||
|
Ok(base64)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::is_image_content_type;
|
use crate::{captcha_espeak_wav_base64, is_image_content_type};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_image() {
|
fn test_image() {
|
||||||
|
@ -241,6 +291,11 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_espeak() {
|
||||||
|
assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
// These helped with testing
|
// These helped with testing
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn test_iframely() {
|
// fn test_iframely() {
|
||||||
|
|
|
@ -140,6 +140,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("/ban", web::post().to(route_post::<BanUser>))
|
.route("/ban", web::post().to(route_post::<BanUser>))
|
||||||
// Account actions. I don't like that they're in /user maybe /accounts
|
// Account actions. I don't like that they're in /user maybe /accounts
|
||||||
.route("/login", web::post().to(route_post::<Login>))
|
.route("/login", web::post().to(route_post::<Login>))
|
||||||
|
.route("/get_captcha", web::get().to(route_post::<GetCaptcha>))
|
||||||
.route(
|
.route(
|
||||||
"/delete_account",
|
"/delete_account",
|
||||||
web::post().to(route_post::<DeleteAccount>),
|
web::post().to(route_post::<DeleteAccount>),
|
||||||
|
|
|
@ -20,6 +20,7 @@ use std::{
|
||||||
pub enum UserOperation {
|
pub enum UserOperation {
|
||||||
Login,
|
Login,
|
||||||
Register,
|
Register,
|
||||||
|
GetCaptcha,
|
||||||
CreateCommunity,
|
CreateCommunity,
|
||||||
CreatePost,
|
CreatePost,
|
||||||
ListCommunities,
|
ListCommunities,
|
||||||
|
|
|
@ -16,6 +16,7 @@ use crate::{
|
||||||
UserId,
|
UserId,
|
||||||
};
|
};
|
||||||
use actix_web::client::Client;
|
use actix_web::client::Client;
|
||||||
|
use lemmy_db::naive_now;
|
||||||
|
|
||||||
/// Chat server sends this messages to session
|
/// Chat server sends this messages to session
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
|
@ -134,6 +135,21 @@ pub struct SessionInfo {
|
||||||
pub ip: IPAddr,
|
pub ip: IPAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message, Debug)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct CaptchaItem {
|
||||||
|
pub uuid: String,
|
||||||
|
pub answer: String,
|
||||||
|
pub expires: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(bool)]
|
||||||
|
pub struct CheckCaptcha {
|
||||||
|
pub uuid: String,
|
||||||
|
pub answer: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
/// `ChatServer` manages chat rooms and responsible for coordinating chat
|
||||||
/// session.
|
/// session.
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
|
@ -158,6 +174,9 @@ pub struct ChatServer {
|
||||||
/// Rate limiting based on rate type and IP addr
|
/// Rate limiting based on rate type and IP addr
|
||||||
rate_limiter: RateLimit,
|
rate_limiter: RateLimit,
|
||||||
|
|
||||||
|
/// A list of the current captchas
|
||||||
|
captchas: Vec<CaptchaItem>,
|
||||||
|
|
||||||
/// An HTTP Client
|
/// An HTTP Client
|
||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
@ -176,6 +195,7 @@ impl ChatServer {
|
||||||
rng: rand::thread_rng(),
|
rng: rand::thread_rng(),
|
||||||
pool,
|
pool,
|
||||||
rate_limiter,
|
rate_limiter,
|
||||||
|
captchas: Vec::new(),
|
||||||
client,
|
client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,6 +461,7 @@ impl ChatServer {
|
||||||
// User ops
|
// User ops
|
||||||
UserOperation::Login => do_user_operation::<Login>(args).await,
|
UserOperation::Login => do_user_operation::<Login>(args).await,
|
||||||
UserOperation::Register => do_user_operation::<Register>(args).await,
|
UserOperation::Register => do_user_operation::<Register>(args).await,
|
||||||
|
UserOperation::GetCaptcha => do_user_operation::<GetCaptcha>(args).await,
|
||||||
UserOperation::GetUserDetails => do_user_operation::<GetUserDetails>(args).await,
|
UserOperation::GetUserDetails => do_user_operation::<GetUserDetails>(args).await,
|
||||||
UserOperation::GetReplies => do_user_operation::<GetReplies>(args).await,
|
UserOperation::GetReplies => do_user_operation::<GetReplies>(args).await,
|
||||||
UserOperation::AddAdmin => do_user_operation::<AddAdmin>(args).await,
|
UserOperation::AddAdmin => do_user_operation::<AddAdmin>(args).await,
|
||||||
|
@ -635,7 +656,7 @@ impl Handler<StandardMessage> for ChatServer {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
match fut.await {
|
match fut.await {
|
||||||
Ok(m) => {
|
Ok(m) => {
|
||||||
info!("Message Sent: {}", m);
|
// info!("Message Sent: {}", m);
|
||||||
Ok(m)
|
Ok(m)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -774,3 +795,30 @@ where
|
||||||
};
|
};
|
||||||
Ok(serde_json::to_string(&response)?)
|
Ok(serde_json::to_string(&response)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Handler<CaptchaItem> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CaptchaItem, _: &mut Context<Self>) {
|
||||||
|
self.captchas.push(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CheckCaptcha> for ChatServer {
|
||||||
|
type Result = bool;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CheckCaptcha, _: &mut Context<Self>) -> Self::Result {
|
||||||
|
// Remove all the ones that are past the expire time
|
||||||
|
self.captchas.retain(|x| x.expires.gt(&naive_now()));
|
||||||
|
|
||||||
|
let check = self
|
||||||
|
.captchas
|
||||||
|
.iter()
|
||||||
|
.any(|r| r.uuid == msg.uuid && r.answer == msg.answer);
|
||||||
|
|
||||||
|
// Remove this uuid so it can't be re-checked (Checks only work once)
|
||||||
|
self.captchas.retain(|x| x.uuid != msg.uuid);
|
||||||
|
|
||||||
|
check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
ui/src/components/create-post.tsx
vendored
2
ui/src/components/create-post.tsx
vendored
|
@ -110,7 +110,7 @@ export class CreatePost extends Component<any, CreatePostState> {
|
||||||
return lastLocation.split('/c/')[1];
|
return lastLocation.split('/c/')[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostCreate(id: number) {
|
handlePostCreate(id: number) {
|
||||||
|
|
131
ui/src/components/login.tsx
vendored
131
ui/src/components/login.tsx
vendored
|
@ -9,6 +9,7 @@ import {
|
||||||
UserOperation,
|
UserOperation,
|
||||||
PasswordResetForm,
|
PasswordResetForm,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
|
GetCaptchaResponse,
|
||||||
WebSocketJsonResponse,
|
WebSocketJsonResponse,
|
||||||
Site,
|
Site,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
|
@ -21,11 +22,8 @@ interface State {
|
||||||
registerForm: RegisterForm;
|
registerForm: RegisterForm;
|
||||||
loginLoading: boolean;
|
loginLoading: boolean;
|
||||||
registerLoading: boolean;
|
registerLoading: boolean;
|
||||||
mathQuestion: {
|
captcha: GetCaptchaResponse;
|
||||||
a: number;
|
captchaPlaying: boolean;
|
||||||
b: number;
|
|
||||||
answer: number;
|
|
||||||
};
|
|
||||||
site: Site;
|
site: Site;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,14 +41,13 @@ export class Login extends Component<any, State> {
|
||||||
password_verify: undefined,
|
password_verify: undefined,
|
||||||
admin: false,
|
admin: false,
|
||||||
show_nsfw: false,
|
show_nsfw: false,
|
||||||
|
captcha_uuid: undefined,
|
||||||
|
captcha_answer: undefined,
|
||||||
},
|
},
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
registerLoading: false,
|
registerLoading: false,
|
||||||
mathQuestion: {
|
captcha: undefined,
|
||||||
a: Math.floor(Math.random() * 10) + 1,
|
captchaPlaying: false,
|
||||||
b: Math.floor(Math.random() * 10) + 1,
|
|
||||||
answer: undefined,
|
|
||||||
},
|
|
||||||
site: {
|
site: {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
@ -81,6 +78,7 @@ export class Login extends Component<any, State> {
|
||||||
);
|
);
|
||||||
|
|
||||||
WebSocketService.Instance.getSite();
|
WebSocketService.Instance.getSite();
|
||||||
|
WebSocketService.Instance.getCaptcha();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -172,6 +170,7 @@ export class Login extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerForm() {
|
registerForm() {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
|
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
|
||||||
|
@ -258,23 +257,37 @@ export class Login extends Component<any, State> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
|
||||||
<label class="col-sm-10 col-form-label" htmlFor="register-math">
|
|
||||||
{i18n.t('what_is')}{' '}
|
|
||||||
{`${this.state.mathQuestion.a} + ${this.state.mathQuestion.b}?`}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="col-sm-2">
|
{this.state.captcha && (
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="col-sm-2" htmlFor="register-captcha">
|
||||||
|
<span class="mr-2">{i18n.t('enter_code')}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
onClick={linkEvent(this, this.handleRegenCaptcha)}
|
||||||
|
>
|
||||||
|
<svg class="icon icon-refresh-cw">
|
||||||
|
<use xlinkHref="#icon-refresh-cw"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
{this.showCaptcha()}
|
||||||
|
<div class="col-sm-6">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
id="register-math"
|
|
||||||
class="form-control"
|
class="form-control"
|
||||||
value={this.state.mathQuestion.answer}
|
id="register-captcha"
|
||||||
onInput={linkEvent(this, this.handleMathAnswerChange)}
|
value={this.state.registerForm.captcha_answer}
|
||||||
|
onInput={linkEvent(
|
||||||
|
this,
|
||||||
|
this.handleRegisterCaptchaAnswerChange
|
||||||
|
)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{this.state.site.enable_nsfw && (
|
{this.state.site.enable_nsfw && (
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
|
@ -295,11 +308,7 @@ export class Login extends Component<any, State> {
|
||||||
)}
|
)}
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button
|
<button type="submit" class="btn btn-secondary">
|
||||||
type="submit"
|
|
||||||
class="btn btn-secondary"
|
|
||||||
disabled={this.mathCheck}
|
|
||||||
>
|
|
||||||
{this.state.registerLoading ? (
|
{this.state.registerLoading ? (
|
||||||
<svg class="icon icon-spinner spin">
|
<svg class="icon icon-spinner spin">
|
||||||
<use xlinkHref="#icon-spinner"></use>
|
<use xlinkHref="#icon-spinner"></use>
|
||||||
|
@ -314,6 +323,36 @@ export class Login extends Component<any, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showCaptcha() {
|
||||||
|
return (
|
||||||
|
<div class="col-sm-4">
|
||||||
|
{this.state.captcha.ok && (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
class="rounded-top img-fluid"
|
||||||
|
src={this.captchaPngSrc()}
|
||||||
|
style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
|
||||||
|
/>
|
||||||
|
{this.state.captcha.ok.wav && (
|
||||||
|
<button
|
||||||
|
class="rounded-bottom btn btn-sm btn-secondary btn-block"
|
||||||
|
style="border-top-right-radius: 0; border-top-left-radius: 0;"
|
||||||
|
title={i18n.t('play_captcha_audio')}
|
||||||
|
onClick={linkEvent(this, this.handleCaptchaPlay)}
|
||||||
|
type="button"
|
||||||
|
disabled={this.state.captchaPlaying}
|
||||||
|
>
|
||||||
|
<svg class="icon icon-play">
|
||||||
|
<use xlinkHref="#icon-play"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
handleLoginSubmit(i: Login, event: any) {
|
handleLoginSubmit(i: Login, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.state.loginLoading = true;
|
i.state.loginLoading = true;
|
||||||
|
@ -335,11 +374,8 @@ export class Login extends Component<any, State> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.state.registerLoading = true;
|
i.state.registerLoading = true;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
|
|
||||||
if (!i.mathCheck) {
|
|
||||||
WebSocketService.Instance.register(i.state.registerForm);
|
WebSocketService.Instance.register(i.state.registerForm);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
handleRegisterUsernameChange(i: Login, event: any) {
|
handleRegisterUsernameChange(i: Login, event: any) {
|
||||||
i.state.registerForm.username = event.target.value;
|
i.state.registerForm.username = event.target.value;
|
||||||
|
@ -369,11 +405,16 @@ export class Login extends Component<any, State> {
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMathAnswerChange(i: Login, event: any) {
|
handleRegisterCaptchaAnswerChange(i: Login, event: any) {
|
||||||
i.state.mathQuestion.answer = event.target.value;
|
i.state.registerForm.captcha_answer = event.target.value;
|
||||||
i.setState(i.state);
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRegenCaptcha(_i: Login, _event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
WebSocketService.Instance.getCaptcha();
|
||||||
|
}
|
||||||
|
|
||||||
handlePasswordReset(i: Login) {
|
handlePasswordReset(i: Login) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let resetForm: PasswordResetForm = {
|
let resetForm: PasswordResetForm = {
|
||||||
|
@ -382,11 +423,21 @@ export class Login extends Component<any, State> {
|
||||||
WebSocketService.Instance.passwordReset(resetForm);
|
WebSocketService.Instance.passwordReset(resetForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
get mathCheck(): boolean {
|
handleCaptchaPlay(i: Login) {
|
||||||
return (
|
event.preventDefault();
|
||||||
this.state.mathQuestion.answer !=
|
let snd = new Audio('data:audio/wav;base64,' + i.state.captcha.ok.wav);
|
||||||
this.state.mathQuestion.a + this.state.mathQuestion.b
|
snd.play();
|
||||||
);
|
i.state.captchaPlaying = true;
|
||||||
|
i.setState(i.state);
|
||||||
|
snd.addEventListener('ended', () => {
|
||||||
|
snd.currentTime = 0;
|
||||||
|
i.state.captchaPlaying = false;
|
||||||
|
i.setState(this.state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
captchaPngSrc() {
|
||||||
|
return `data:image/png;base64,${this.state.captcha.ok.png}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage(msg: WebSocketJsonResponse) {
|
parseMessage(msg: WebSocketJsonResponse) {
|
||||||
|
@ -394,6 +445,9 @@ export class Login extends Component<any, State> {
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
toast(i18n.t(msg.error), 'danger');
|
toast(i18n.t(msg.error), 'danger');
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
this.state.registerForm.captcha_answer = undefined;
|
||||||
|
// Refetch another captcha
|
||||||
|
WebSocketService.Instance.getCaptcha();
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -412,6 +466,13 @@ export class Login extends Component<any, State> {
|
||||||
UserService.Instance.login(data);
|
UserService.Instance.login(data);
|
||||||
WebSocketService.Instance.userJoin();
|
WebSocketService.Instance.userJoin();
|
||||||
this.props.history.push('/communities');
|
this.props.history.push('/communities');
|
||||||
|
} else if (res.op == UserOperation.GetCaptcha) {
|
||||||
|
let data = res.data as GetCaptchaResponse;
|
||||||
|
if (data.ok) {
|
||||||
|
this.state.captcha = data;
|
||||||
|
this.state.registerForm.captcha_uuid = data.ok.uuid;
|
||||||
|
this.setState(this.state);
|
||||||
|
}
|
||||||
} else if (res.op == UserOperation.PasswordReset) {
|
} else if (res.op == UserOperation.PasswordReset) {
|
||||||
toast(i18n.t('reset_password_mail_sent'));
|
toast(i18n.t('reset_password_mail_sent'));
|
||||||
} else if (res.op == UserOperation.GetSite) {
|
} else if (res.op == UserOperation.GetSite) {
|
||||||
|
|
3
ui/src/components/setup.tsx
vendored
3
ui/src/components/setup.tsx
vendored
|
@ -29,6 +29,9 @@ export class Setup extends Component<any, State> {
|
||||||
password_verify: undefined,
|
password_verify: undefined,
|
||||||
admin: true,
|
admin: true,
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
|
// The first admin signup doesn't need a captcha
|
||||||
|
captcha_uuid: '',
|
||||||
|
captcha_answer: '',
|
||||||
},
|
},
|
||||||
doneRegisteringUser: false,
|
doneRegisteringUser: false,
|
||||||
userLoading: false,
|
userLoading: false,
|
||||||
|
|
6
ui/src/components/symbols.tsx
vendored
6
ui/src/components/symbols.tsx
vendored
|
@ -15,6 +15,12 @@ export class Symbols extends Component<any, any> {
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
|
<symbol id="icon-refresh-cw" viewBox="0 0 24 24">
|
||||||
|
<path d="M4.453 9.334c0.737-2.083 2.247-3.669 4.096-4.552s4.032-1.059 6.114-0.322c1.186 0.42 2.206 1.088 2.983 1.88l2.83 2.66h-3.476c-0.552 0-1 0.448-1 1s0.448 1 1 1h5.997c0.005 0 0.009 0 0.014 0 0.137-0.001 0.268-0.031 0.386-0.082 0.119-0.051 0.229-0.126 0.324-0.225 0.012-0.013 0.024-0.026 0.036-0.039 0.075-0.087 0.133-0.183 0.173-0.285s0.064-0.211 0.069-0.326c0.001-0.015 0.001-0.029 0.001-0.043v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v3.689l-2.926-2.749c-0.992-1.010-2.271-1.843-3.743-2.364-2.603-0.921-5.335-0.699-7.643 0.402s-4.199 3.086-5.12 5.689c-0.185 0.52 0.088 1.091 0.608 1.276s1.092-0.088 1.276-0.609zM2 16.312l2.955 2.777c1.929 1.931 4.49 2.908 7.048 2.909s5.119-0.975 7.072-2.927c1.104-1.104 1.901-2.407 2.361-3.745 0.18-0.522-0.098-1.091-0.621-1.271s-1.091 0.098-1.271 0.621c-0.361 1.050-0.993 2.091-1.883 2.981-1.563 1.562-3.609 2.342-5.657 2.342s-4.094-0.782-5.679-2.366l-2.8-2.633h3.475c0.552 0 1-0.448 1-1s-0.448-1-1-1h-5.997c-0.005 0-0.009 0-0.014 0-0.137 0.001-0.268 0.031-0.386 0.082-0.119 0.051-0.229 0.126-0.324 0.225-0.012 0.013-0.024 0.026-0.036 0.039-0.075 0.087-0.133 0.183-0.173 0.285s-0.064 0.211-0.069 0.326c-0.001 0.015-0.001 0.029-0.001 0.043v6c0 0.552 0.448 1 1 1s1-0.448 1-1z"></path>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="icon-play" viewBox="0 0 24 24">
|
||||||
|
<path d="M5.541 2.159c-0.153-0.1-0.34-0.159-0.541-0.159-0.552 0-1 0.448-1 1v18c-0.001 0.182 0.050 0.372 0.159 0.541 0.299 0.465 0.917 0.599 1.382 0.3l14-9c0.114-0.072 0.219-0.174 0.3-0.3 0.299-0.465 0.164-1.083-0.3-1.382zM6 4.832l11.151 7.168-11.151 7.168z"></path>
|
||||||
|
</symbol>
|
||||||
<symbol id="icon-strikethrough" viewBox="0 0 28 28">
|
<symbol id="icon-strikethrough" viewBox="0 0 28 28">
|
||||||
<path d="M27.5 14c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-27c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h27zM7.547 13c-0.297-0.375-0.562-0.797-0.797-1.25-0.5-1.016-0.75-2-0.75-2.938 0-1.906 0.703-3.5 2.094-4.828s3.437-1.984 6.141-1.984c0.594 0 1.453 0.109 2.609 0.297 0.688 0.125 1.609 0.375 2.766 0.75 0.109 0.406 0.219 1.031 0.328 1.844 0.141 1.234 0.219 2.187 0.219 2.859 0 0.219-0.031 0.453-0.078 0.703l-0.187 0.047-1.313-0.094-0.219-0.031c-0.531-1.578-1.078-2.641-1.609-3.203-0.922-0.953-2.031-1.422-3.281-1.422-1.188 0-2.141 0.313-2.844 0.922s-1.047 1.375-1.047 2.281c0 0.766 0.344 1.484 1.031 2.188s2.141 1.375 4.359 2.016c0.75 0.219 1.641 0.562 2.703 1.031 0.562 0.266 1.062 0.531 1.484 0.812h-11.609zM15.469 17h6.422c0.078 0.438 0.109 0.922 0.109 1.437 0 1.125-0.203 2.234-0.641 3.313-0.234 0.578-0.594 1.109-1.109 1.625-0.375 0.359-0.938 0.781-1.703 1.266-0.781 0.469-1.563 0.828-2.391 1.031-0.828 0.219-1.875 0.328-3.172 0.328-0.859 0-1.891-0.031-3.047-0.359l-2.188-0.625c-0.609-0.172-0.969-0.313-1.125-0.438-0.063-0.063-0.125-0.172-0.125-0.344v-0.203c0-0.125 0.031-0.938-0.031-2.438-0.031-0.781 0.031-1.328 0.031-1.641v-0.688l1.594-0.031c0.578 1.328 0.844 2.125 1.016 2.406 0.375 0.609 0.797 1.094 1.25 1.469s1 0.672 1.641 0.891c0.625 0.234 1.328 0.344 2.063 0.344 0.656 0 1.391-0.141 2.172-0.422 0.797-0.266 1.437-0.719 1.906-1.344 0.484-0.625 0.734-1.297 0.734-2.016 0-0.875-0.422-1.687-1.266-2.453-0.344-0.297-1.062-0.672-2.141-1.109z"></path>
|
<path d="M27.5 14c0.281 0 0.5 0.219 0.5 0.5v1c0 0.281-0.219 0.5-0.5 0.5h-27c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h27zM7.547 13c-0.297-0.375-0.562-0.797-0.797-1.25-0.5-1.016-0.75-2-0.75-2.938 0-1.906 0.703-3.5 2.094-4.828s3.437-1.984 6.141-1.984c0.594 0 1.453 0.109 2.609 0.297 0.688 0.125 1.609 0.375 2.766 0.75 0.109 0.406 0.219 1.031 0.328 1.844 0.141 1.234 0.219 2.187 0.219 2.859 0 0.219-0.031 0.453-0.078 0.703l-0.187 0.047-1.313-0.094-0.219-0.031c-0.531-1.578-1.078-2.641-1.609-3.203-0.922-0.953-2.031-1.422-3.281-1.422-1.188 0-2.141 0.313-2.844 0.922s-1.047 1.375-1.047 2.281c0 0.766 0.344 1.484 1.031 2.188s2.141 1.375 4.359 2.016c0.75 0.219 1.641 0.562 2.703 1.031 0.562 0.266 1.062 0.531 1.484 0.812h-11.609zM15.469 17h6.422c0.078 0.438 0.109 0.922 0.109 1.437 0 1.125-0.203 2.234-0.641 3.313-0.234 0.578-0.594 1.109-1.109 1.625-0.375 0.359-0.938 0.781-1.703 1.266-0.781 0.469-1.563 0.828-2.391 1.031-0.828 0.219-1.875 0.328-3.172 0.328-0.859 0-1.891-0.031-3.047-0.359l-2.188-0.625c-0.609-0.172-0.969-0.313-1.125-0.438-0.063-0.063-0.125-0.172-0.125-0.344v-0.203c0-0.125 0.031-0.938-0.031-2.438-0.031-0.781 0.031-1.328 0.031-1.641v-0.688l1.594-0.031c0.578 1.328 0.844 2.125 1.016 2.406 0.375 0.609 0.797 1.094 1.25 1.469s1 0.672 1.641 0.891c0.625 0.234 1.328 0.344 2.063 0.344 0.656 0 1.391-0.141 2.172-0.422 0.797-0.266 1.437-0.719 1.906-1.344 0.484-0.625 0.734-1.297 0.734-2.016 0-0.875-0.422-1.687-1.266-2.453-0.344-0.297-1.062-0.672-2.141-1.109z"></path>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
12
ui/src/interfaces.ts
vendored
12
ui/src/interfaces.ts
vendored
|
@ -1,6 +1,7 @@
|
||||||
export enum UserOperation {
|
export enum UserOperation {
|
||||||
Login,
|
Login,
|
||||||
Register,
|
Register,
|
||||||
|
GetCaptcha,
|
||||||
CreateCommunity,
|
CreateCommunity,
|
||||||
CreatePost,
|
CreatePost,
|
||||||
ListCommunities,
|
ListCommunities,
|
||||||
|
@ -572,6 +573,16 @@ export interface RegisterForm {
|
||||||
password_verify: string;
|
password_verify: string;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
show_nsfw: boolean;
|
show_nsfw: boolean;
|
||||||
|
captcha_uuid?: string;
|
||||||
|
captcha_answer?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetCaptchaResponse {
|
||||||
|
ok?: {
|
||||||
|
png: string;
|
||||||
|
wav?: string;
|
||||||
|
uuid: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
|
@ -1010,6 +1021,7 @@ type ResponseType =
|
||||||
| CommentResponse
|
| CommentResponse
|
||||||
| UserMentionResponse
|
| UserMentionResponse
|
||||||
| LoginResponse
|
| LoginResponse
|
||||||
|
| GetCaptchaResponse
|
||||||
| GetModlogResponse
|
| GetModlogResponse
|
||||||
| SearchResponse
|
| SearchResponse
|
||||||
| BanFromCommunityResponse
|
| BanFromCommunityResponse
|
||||||
|
|
4
ui/src/services/WebSocketService.ts
vendored
4
ui/src/services/WebSocketService.ts
vendored
|
@ -115,6 +115,10 @@ export class WebSocketService {
|
||||||
this.ws.send(this.wsSendWrapper(UserOperation.Register, registerForm));
|
this.ws.send(this.wsSendWrapper(UserOperation.Register, registerForm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCaptcha() {
|
||||||
|
this.ws.send(this.wsSendWrapper(UserOperation.GetCaptcha, {}));
|
||||||
|
}
|
||||||
|
|
||||||
public createCommunity(form: CommunityForm) {
|
public createCommunity(form: CommunityForm) {
|
||||||
this.setAuth(form);
|
this.setAuth(form);
|
||||||
this.ws.send(this.wsSendWrapper(UserOperation.CreateCommunity, form));
|
this.ws.send(this.wsSendWrapper(UserOperation.CreateCommunity, form));
|
||||||
|
|
5
ui/translations/en.json
vendored
5
ui/translations/en.json
vendored
|
@ -264,6 +264,8 @@
|
||||||
"password_incorrect": "Password incorrect.",
|
"password_incorrect": "Password incorrect.",
|
||||||
"passwords_dont_match": "Passwords do not match.",
|
"passwords_dont_match": "Passwords do not match.",
|
||||||
"no_password_reset": "You will not be able to reset your password without an email.",
|
"no_password_reset": "You will not be able to reset your password without an email.",
|
||||||
|
"captcha_incorrect": "Captcha incorrect.",
|
||||||
|
"enter_code": "Enter Code",
|
||||||
"invalid_username": "Invalid username.",
|
"invalid_username": "Invalid username.",
|
||||||
"admin_already_created": "Sorry, there's already an admin.",
|
"admin_already_created": "Sorry, there's already an admin.",
|
||||||
"user_already_exists": "User already exists.",
|
"user_already_exists": "User already exists.",
|
||||||
|
@ -281,5 +283,6 @@
|
||||||
"cake_day_title": "Cake day:",
|
"cake_day_title": "Cake day:",
|
||||||
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
"cake_day_info": "It's {{ creator_name }}'s cake day today!",
|
||||||
"invalid_post_title": "Invalid post title",
|
"invalid_post_title": "Invalid post title",
|
||||||
"invalid_url": "Invalid URL."
|
"invalid_url": "Invalid URL.",
|
||||||
|
"play_captcha_audio": "Play Captcha Audio"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue