mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2026-02-11 10:05:41 +00:00
Compare commits
228 Commits
feat-perce
...
fix-gh-192
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f18a25b97 | ||
|
|
9cd7cf3a02 | ||
|
|
b624c7d326 | ||
|
|
60a6ad92d9 | ||
|
|
3c0ed48aac | ||
|
|
a05f8ec6f0 | ||
|
|
5c29c2e71e | ||
|
|
721d880a7a | ||
|
|
4ad2fe36cc | ||
|
|
140889ff6d | ||
|
|
324d13f0d4 | ||
|
|
840f14868a | ||
|
|
5c476090d1 | ||
|
|
f1661d0f7f | ||
|
|
6eda1e8a34 | ||
|
|
848c3f80bf | ||
|
|
a66f27c084 | ||
|
|
c886989c25 | ||
|
|
982243aff0 | ||
|
|
31f96081a2 | ||
|
|
8507d130b0 | ||
|
|
355853b5ea | ||
|
|
ca6193241b | ||
|
|
1a73c2d10b | ||
|
|
c0695a8b40 | ||
|
|
4c71f3ae5e | ||
|
|
fb0b9ff71c | ||
|
|
29d4cfff7d | ||
|
|
85466e360c | ||
|
|
df242c8fda | ||
|
|
628166ea15 | ||
|
|
45c7b39c32 | ||
|
|
1b379f47ac | ||
|
|
d54404a493 | ||
|
|
464b25d957 | ||
|
|
9aa5789591 | ||
|
|
e5dd0711ca | ||
|
|
c472cf678b | ||
|
|
adfb887657 | ||
|
|
a4de37c7d2 | ||
|
|
ef1afa1fa9 | ||
|
|
839444834e | ||
|
|
9c20f2d222 | ||
|
|
8c8dd9c1d3 | ||
|
|
624e95f3ec | ||
|
|
5b7e4ba1b6 | ||
|
|
bb18229263 | ||
|
|
1ce1ac3b48 | ||
|
|
a0c9790203 | ||
|
|
e19bcc88ee | ||
|
|
1f7326cacc | ||
|
|
d390bb10a8 | ||
|
|
1f7bcf018f | ||
|
|
ed5885f7bf | ||
|
|
dcc9c4c540 | ||
|
|
a9f97ec423 | ||
|
|
5f556c3c21 | ||
|
|
9a8c9c46f2 | ||
|
|
9b62a7cd13 | ||
|
|
e8b3debfde | ||
|
|
f54cfb42ff | ||
|
|
8f9db61b5f | ||
|
|
adb71c19c0 | ||
|
|
2d26c58dc8 | ||
|
|
c91c265ab2 | ||
|
|
7cdbf18fd1 | ||
|
|
ba601720f4 | ||
|
|
8fe6f8ce13 | ||
|
|
aa3451a1de | ||
|
|
13158c4660 | ||
|
|
81016e6c66 | ||
|
|
a319325f5a | ||
|
|
af78594b75 | ||
|
|
e87681086d | ||
|
|
bf82127d58 | ||
|
|
a63ac30afd | ||
|
|
210e08dbec | ||
|
|
dba69111bf | ||
|
|
dc746388c6 | ||
|
|
53fd774a3f | ||
|
|
3f8a4cacd2 | ||
|
|
acd6eebcfe | ||
|
|
adef372720 | ||
|
|
4b025a94c8 | ||
|
|
d38fd374f4 | ||
|
|
6ee914e797 | ||
|
|
37799184bf | ||
|
|
e5690ff4ea | ||
|
|
9662d03450 | ||
|
|
151aba467a | ||
|
|
d010eb000d | ||
|
|
69fc6c0d90 | ||
|
|
fa6493ec43 | ||
|
|
e849ae43e7 | ||
|
|
9fa1c45492 | ||
|
|
76ba9ded00 | ||
|
|
403a47856e | ||
|
|
d352bc691d | ||
|
|
127c61b455 | ||
|
|
16da3d258f | ||
|
|
4c5401941c | ||
|
|
80f9748aa7 | ||
|
|
44efdd3541 | ||
|
|
1dcad0d507 | ||
|
|
7f6a703556 | ||
|
|
f8da5ca3bf | ||
|
|
7c643e013e | ||
|
|
be4c7779fa | ||
|
|
a9e11a8c56 | ||
|
|
567a8c6b60 | ||
|
|
5da73093c4 | ||
|
|
0d37495a61 | ||
|
|
c7a30fe62e | ||
|
|
26f23f6700 | ||
|
|
b851bdb2b0 | ||
|
|
83c90c78e2 | ||
|
|
9768998926 | ||
|
|
78186f13e6 | ||
|
|
5500483e1b | ||
|
|
0a82a62adc | ||
|
|
fac6c92812 | ||
|
|
3fea35b481 | ||
|
|
7d564973b7 | ||
|
|
718d8d4c37 | ||
|
|
b8143fb634 | ||
|
|
9ca6992e16 | ||
|
|
cfd88cd20b | ||
|
|
4f28931875 | ||
|
|
3f3fd3ce83 | ||
|
|
26c83e1af1 | ||
|
|
8ea48c6f2a | ||
|
|
6f9489403a | ||
|
|
1effdc5a18 | ||
|
|
8bfa53cc4f | ||
|
|
4046d389ac | ||
|
|
09d2ab4e75 | ||
|
|
20dbcb5b8f | ||
|
|
2a71dfe459 | ||
|
|
047a0d1e2c | ||
|
|
7584d2a574 | ||
|
|
beb3d8a8e2 | ||
|
|
d59ce15043 | ||
|
|
d350f8d2a9 | ||
|
|
f02a79637f | ||
|
|
ce0eb26422 | ||
|
|
1ee5f01444 | ||
|
|
0ab3599c63 | ||
|
|
4278335fdd | ||
|
|
ef5e2fee27 | ||
|
|
795d29bcb0 | ||
|
|
b5766622fb | ||
|
|
ab679e71d2 | ||
|
|
ac9aecdb95 | ||
|
|
22bd14056c | ||
|
|
8810931d10 | ||
|
|
e11d71a5fa | ||
|
|
d9509cc4ce | ||
|
|
0c2915f334 | ||
|
|
71fc1b4d46 | ||
|
|
36dc200193 | ||
|
|
589b476e43 | ||
|
|
43da103f34 | ||
|
|
c0899265c2 | ||
|
|
c827208144 | ||
|
|
58e2dcb9d4 | ||
|
|
a0c89d0e59 | ||
|
|
86a12c5c6a | ||
|
|
30fa1bb165 | ||
|
|
4606da212d | ||
|
|
8d6de7ed5a | ||
|
|
2ae9c51da3 | ||
|
|
2b6f039dd4 | ||
|
|
eb65259bc1 | ||
|
|
3fdf215459 | ||
|
|
ef8e07c44b | ||
|
|
bacdbdc9e8 | ||
|
|
d9cf53d399 | ||
|
|
d619092598 | ||
|
|
54ed53e03d | ||
|
|
6bd8cfb711 | ||
|
|
ef652541ee | ||
|
|
474437c360 | ||
|
|
1956e97503 | ||
|
|
a5d4dad508 | ||
|
|
afb281c93a | ||
|
|
182635bad1 | ||
|
|
9922c23322 | ||
|
|
2a92e8f672 | ||
|
|
581e6f9440 | ||
|
|
3a7e71b499 | ||
|
|
91955501e2 | ||
|
|
cb72b0adfa | ||
|
|
c1fe45d409 | ||
|
|
59e339f287 | ||
|
|
ddcde9cf18 | ||
|
|
65c92ee96e | ||
|
|
40766ff44f | ||
|
|
ddc3008009 | ||
|
|
cba056aba6 | ||
|
|
16c975d2ec | ||
|
|
f67fdf917e | ||
|
|
e1746223c8 | ||
|
|
ae08c09702 | ||
|
|
1c9e242d7e | ||
|
|
3cb0ca38ec | ||
|
|
3cd38511fa | ||
|
|
d765bd7491 | ||
|
|
8e33e75659 | ||
|
|
c00857dd28 | ||
|
|
9cf0eb596a | ||
|
|
d010a1cbd4 | ||
|
|
453be36201 | ||
|
|
d4967626ee | ||
|
|
2ee0a0ae5f | ||
|
|
5722a5327c | ||
|
|
239e0bfd47 | ||
|
|
790fd8f393 | ||
|
|
c70a30e640 | ||
|
|
3b8051159d | ||
|
|
91c235b3f4 | ||
|
|
9bcf6af3ab | ||
|
|
e0d6188853 | ||
|
|
1b2096d955 | ||
|
|
13c3066b86 | ||
|
|
da934f5ba7 | ||
|
|
26ad05d284 | ||
|
|
b6326603d1 | ||
|
|
8bed3cb352 |
43
.env.docker-compose
Normal file
43
.env.docker-compose
Normal file
@@ -0,0 +1,43 @@
|
||||
MCAPTCHA_debug=false
|
||||
MCAPTCHA_commercial=false
|
||||
MCAPTCHA_source_code=https://github.com/mCaptcha/mCaptcha
|
||||
MCAPTCHA_allow_registration=false
|
||||
MCAPTCHA_allow_demo=false
|
||||
|
||||
# database
|
||||
DATABASE_URL=postgres://postgres:password@mcaptcha_postgres:5432/postgres
|
||||
MCAPTCHA_database_POOL=4
|
||||
|
||||
# redis
|
||||
MCAPTCHA_redis_URL=redis://mcaptcha_redis
|
||||
MCAPTCHA_redis_POOL=4
|
||||
|
||||
# server
|
||||
PORT=7000
|
||||
MCAPTCHA_server_DOMAIN=localhost
|
||||
MCAPTCHA__server_COOKIE_SECRET=pleasereplacethiswithrandomstring # PLEASE SET RANDOM STRING. MIN LENGTH=32
|
||||
MCAPTCHA__server_IP= 0.0.0.0
|
||||
|
||||
|
||||
# captcha
|
||||
MCAPTCHA_captcha_SALT=pleasereplacethiswithrandomstring # PLEASE SET RANDOM STRING. MIN LENGTH=32
|
||||
MCAPTCHA_captcha_GC=30
|
||||
MCAPTCHA_captcha_RUNNERS=4
|
||||
MCAPTCHA_captcha_QUEUE_LENGTH=2000
|
||||
MCAPTCHA_captcha_ENABLE_STATS=true
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty=50000 # almost instant solution
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty=3000000 # greater than 3.5s
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty=5000000 # roughly 1.5s
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration=30 # cooldown period in seconds
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time=1 # almost instant solution
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time=3
|
||||
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time=5
|
||||
|
||||
|
||||
# SMTP
|
||||
#MCAPTCHA_smtp_FROM=
|
||||
#MCAPTCHA_smtp_REPLY=
|
||||
#MCAPTCHA_smtp_URL=
|
||||
#MCAPTCHA_smtp_USERNAME=
|
||||
#MCAPTCHA_smtp_PASSWORD=
|
||||
#MCAPTCHA_smtp_PORT=
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -84,7 +84,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "20.0.0"
|
||||
node-version: "22"
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
|
||||
125
.github/workflows/release.yml
vendored
Normal file
125
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
release:
|
||||
type: [published]
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
name: x86_64-unknown-linux-gnu
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
mcaptcha-redis:
|
||||
image: mcaptcha/cache
|
||||
ports:
|
||||
- 6379:6379
|
||||
mcaptcha-smtp:
|
||||
image: maildev/maildev
|
||||
env:
|
||||
MAILDEV_WEB_PORT: "1080"
|
||||
MAILDEV_INCOMING_USER: "admin"
|
||||
MAILDEV_INCOMING_PASS: "password"
|
||||
ports:
|
||||
- 1080:1080
|
||||
- 10025:1025
|
||||
|
||||
maria:
|
||||
image: mariadb:10
|
||||
env:
|
||||
MARIADB_USER: "maria"
|
||||
MARIADB_PASSWORD: "password"
|
||||
MARIADB_ROOT_PASSWORD: "password"
|
||||
MARIADB_DATABASE: "maria"
|
||||
options: >-
|
||||
--health-cmd="mysqladmin ping"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=10
|
||||
ports:
|
||||
- 3306:3306
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: configure GPG key
|
||||
run: echo -n "$RELEASE_BOT_GPG_SIGNING_KEY" | gpg --batch --import --pinentry-mode loopback
|
||||
env:
|
||||
RELEASE_BOT_GPG_SIGNING_KEY: ${{ secrets.RELEASE_BOT_GPG_SIGNING_KEY }}
|
||||
|
||||
- name: Set release version
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: load env
|
||||
run: |
|
||||
source .env_sample \
|
||||
&& echo "POSTGRES_DATABASE_URL=$POSTGRES_DATABASE_URL" >> $GITHUB_ENV \
|
||||
&& echo "MARIA_DATABASE_URL=$MARIA_DATABASE_URL" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "20.0.0"
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: install nightwatch dep
|
||||
run: sudo apt-get install xvfb
|
||||
|
||||
- name: Run migrations
|
||||
run: make migrate
|
||||
env:
|
||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
|
||||
- name: build
|
||||
run: make
|
||||
env:
|
||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
|
||||
- name: lint frontend
|
||||
run: yarn lint
|
||||
|
||||
- name: run tests
|
||||
run: make test
|
||||
env:
|
||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
|
||||
- name: run integration tests
|
||||
run: make test.integration
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: mcaptcha
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: build docker images
|
||||
run: docker build -t mcaptcha/mcaptcha:${RELEASE_VERSION} .
|
||||
|
||||
- name: publish docker images
|
||||
run: docker push mcaptcha/mcaptcha:${RELEASE_VERSION}
|
||||
|
||||
- name: publish bins
|
||||
run: ./scripts/publish.sh publish $RELEASE_VERSION latest $DUMBSERVE_PASSWORD
|
||||
env:
|
||||
DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
|
||||
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
|
||||
64
.github/workflows/tagged-release.yml
vendored
64
.github/workflows/tagged-release.yml
vendored
@@ -1,32 +1,32 @@
|
||||
name: Create binary for release
|
||||
|
||||
# Only on tags that start with a "v"
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-pc-windows-gnu
|
||||
archive: zip
|
||||
- target: x86_64-unknown-linux-musl
|
||||
archive: tar.gz tar.xz
|
||||
- target: x86_64-apple-darwin
|
||||
archive: zip
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Compile and release
|
||||
uses: rust-build/rust-build.action@v1.3.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
RUSTTARGET: ${{ matrix.target }}
|
||||
ARCHIVE_TYPES: ${{ matrix.archive }}
|
||||
#name: Create binary for release
|
||||
#
|
||||
## Only on tags that start with a "v"
|
||||
#on:
|
||||
# push:
|
||||
# tags:
|
||||
# - "v*"
|
||||
#
|
||||
#jobs:
|
||||
# build:
|
||||
# runs-on: ubuntu-latest
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# include:
|
||||
# - target: x86_64-pc-windows-gnu
|
||||
# archive: zip
|
||||
# - target: x86_64-unknown-linux-musl
|
||||
# archive: tar.gz tar.xz
|
||||
# - target: x86_64-apple-darwin
|
||||
# archive: zip
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v3
|
||||
#
|
||||
# - name: Compile and release
|
||||
# uses: rust-build/rust-build.action@v1.3.2
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# RUSTTARGET: ${{ matrix.target }}
|
||||
# ARCHIVE_TYPES: ${{ matrix.archive }}
|
||||
|
||||
29
.woodpecker.yaml
Normal file
29
.woodpecker.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
steps:
|
||||
frontend:
|
||||
image: node:22
|
||||
commands:
|
||||
- make frontend
|
||||
when:
|
||||
- event: [pull_request, push]
|
||||
- branch:
|
||||
include: [woodpecker-pipeline]
|
||||
exclude:
|
||||
- renovate/\*
|
||||
volumes:
|
||||
- /var/lib/woodpercker-cache/mcaptcha/yarn:/root/.cache/yarn
|
||||
|
||||
backend:
|
||||
image: rust:latest
|
||||
when:
|
||||
- event: [pull_request, push]
|
||||
- branch:
|
||||
include: [woodpecker-pipeline]
|
||||
exclude:
|
||||
- renovate/\*
|
||||
commands:
|
||||
- cargo --version
|
||||
- make cache-bust
|
||||
- cargo build --release
|
||||
volumes:
|
||||
- /var/lib/woodpercker-cache/mcaptcha/cargo:/root/.cargo
|
||||
- /var/lib/woodpercker-cache/mcaptcha/target:/woodpecker/src/git.batsense.net/mCaptcha/mCaptcha/target
|
||||
2962
Cargo.lock
generated
2962
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@@ -28,23 +28,23 @@ actix = "0.13"
|
||||
actix-identity = "0.4.0"
|
||||
actix-http = "3.0.4"
|
||||
actix-rt = "2"
|
||||
actix-cors = "0.6.1"
|
||||
actix-cors = "0.7.0"
|
||||
actix-service = "2.0.0"
|
||||
async-trait = "0.1.51"
|
||||
mime_guess = "2.0.3"
|
||||
rust-embed = "6.4.0"
|
||||
rust-embed = "8.0.0"
|
||||
libcachebust = "0.3.0"
|
||||
|
||||
futures = "0.3.15"
|
||||
tokio = { version = "1.14", features = ["sync"]}
|
||||
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "time", "mysql"] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "postgres", "time", "mysql"] }
|
||||
argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
|
||||
#argon2-creds = { version="*", path = "../../argon2-creds/" }
|
||||
config = "0.13"
|
||||
validator = { version = "0.15", features = ["derive"]}
|
||||
config = "0.15"
|
||||
validator = { version = "0.20", features = ["derive"]}
|
||||
|
||||
derive_builder = "0.11"
|
||||
derive_builder = "0.20"
|
||||
derive_more = "0.99"
|
||||
|
||||
serde = "1"
|
||||
@@ -53,7 +53,7 @@ serde_json = "1"
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.0"
|
||||
|
||||
pretty_env_logger = "0.4"
|
||||
pretty_env_logger = "0.5"
|
||||
log = "0.4"
|
||||
|
||||
lazy_static = "1.4"
|
||||
@@ -69,7 +69,7 @@ mime = "0.3.16"
|
||||
|
||||
num_cpus = "1.13.1"
|
||||
|
||||
lettre = { version = "0.10.0-rc.3", features = [
|
||||
lettre = { version = "0.11.0", features = [
|
||||
"builder",
|
||||
"tokio1",
|
||||
"tokio1-native-tls",
|
||||
@@ -78,7 +78,7 @@ lettre = { version = "0.10.0-rc.3", features = [
|
||||
|
||||
openssl = { version = "0.10.48", features = ["vendored"] }
|
||||
uuid = { version = "1.4.0", features = ["v4", "serde"] }
|
||||
reqwest = { version = "0.11.18", features = ["json", "gzip"] }
|
||||
reqwest = { version = "0.12.0", features = ["json", "gzip"] }
|
||||
|
||||
|
||||
[dependencies.db-core]
|
||||
@@ -101,10 +101,10 @@ features = ["actix_identity_backend"]
|
||||
|
||||
[build-dependencies]
|
||||
serde_json = "1"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "time", "mysql" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "postgres", "time", "mysql" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
mcaptcha_pow_sha256 = "0.4"
|
||||
mcaptcha_pow_sha256 = "0.5"
|
||||
awc = "3.0.0"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
FROM node:20 as frontend
|
||||
FROM node:22 as frontend
|
||||
RUN set -ex; \
|
||||
apt-get update; \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
@@ -29,8 +29,12 @@ RUN cargo --version
|
||||
RUN make cache-bust
|
||||
RUN cargo build --release
|
||||
|
||||
FROM debian:bookworm as mCaptcha
|
||||
FROM debian:trixie as mCaptcha
|
||||
LABEL org.opencontainers.image.source https://github.com/mCaptcha/mCaptcha
|
||||
RUN set -ex; \
|
||||
apt-get update; \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get install -y --no-install-recommends curl
|
||||
RUN useradd -ms /bin/bash -u 1001 mcaptcha
|
||||
WORKDIR /home/mcaptcha
|
||||
COPY --from=rust /src/target/release/mcaptcha /usr/local/bin/
|
||||
|
||||
@@ -34,8 +34,11 @@ enable_stats = true
|
||||
|
||||
[captcha.default_difficulty_strategy]
|
||||
avg_traffic_difficulty = 50000 # almost instant solution
|
||||
avg_traffic_time = 1 # almost instant solution
|
||||
peak_sustainable_traffic_difficulty = 3000000 # roughly 1.5s
|
||||
peak_sustainable_traffic_time = 3
|
||||
broke_my_site_traffic_difficulty = 5000000 # greater than 3.5s
|
||||
broke_my_site_traffic_time = 5
|
||||
duration = 30 # cooldown period in seconds
|
||||
|
||||
[database]
|
||||
|
||||
@@ -10,7 +10,7 @@ authors = ["realaravinth <realaravinth@batsense.net>"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.51"
|
||||
thiserror = "1.0.30"
|
||||
thiserror = "2.0.0"
|
||||
serde = { version = "1", features = ["derive"]}
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
libmcaptcha = "0.2.4"
|
||||
|
||||
@@ -202,6 +202,13 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
||||
captcha_key: &str,
|
||||
) -> DBResult<TrafficPattern>;
|
||||
|
||||
/// Get all easy captcha configurations on instance
|
||||
async fn get_all_easy_captchas(
|
||||
&self,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> DBResult<Vec<EasyCaptcha>>;
|
||||
|
||||
/// Delete traffic configuration
|
||||
async fn delete_traffic_pattern(
|
||||
&self,
|
||||
@@ -383,6 +390,19 @@ pub struct AddNotification<'a> {
|
||||
pub message: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||
/// Represents Easy captcha configuration
|
||||
pub struct EasyCaptcha {
|
||||
/// traffic pattern of easy captcha
|
||||
pub traffic_pattern: TrafficPattern,
|
||||
/// captcha key/sitekey
|
||||
pub key: String,
|
||||
/// captcha description
|
||||
pub description: String,
|
||||
/// Owner of the captcha configuration
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||
/// User's traffic pattern; used in generating a captcha configuration
|
||||
pub struct TrafficPattern {
|
||||
|
||||
@@ -223,6 +223,11 @@ pub async fn database_works<'a, T: MCDatabase>(
|
||||
tp
|
||||
);
|
||||
|
||||
// get all traffic patterns
|
||||
let patterns = db.get_all_easy_captchas(10, 0).await.unwrap();
|
||||
assert_eq!(patterns.get(0).as_ref().unwrap().key, c.key);
|
||||
assert_eq!(&patterns.get(0).unwrap().traffic_pattern, tp);
|
||||
|
||||
// delete traffic pattern
|
||||
db.delete_traffic_pattern(p.username, c.key).await.unwrap();
|
||||
assert!(
|
||||
|
||||
@@ -10,4 +10,4 @@ authors = ["realaravinth <realaravinth@batsense.net>"]
|
||||
|
||||
[dependencies]
|
||||
actix-rt = "2"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "time", "mysql" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "postgres", "time", "mysql" ] }
|
||||
|
||||
@@ -8,7 +8,6 @@ use std::env;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[actix_rt::main]
|
||||
async fn main() {
|
||||
//TODO featuregate sqlite and postgres
|
||||
|
||||
80
db/db-sqlx-maria/.sqlx/query-d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64.json
generated
Normal file
80
db/db-sqlx-maria/.sqlx/query-d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64.json
generated
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "SELECT \n mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,\n mcaptcha_config.name,\n mcaptcha_users.name as username,\n mcaptcha_config.captcha_key\n FROM \n mcaptcha_sitekey_user_provided_avg_traffic \n INNER JOIN\n mcaptcha_config\n ON\n mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id\n INNER JOIN\n mcaptcha_users\n ON\n mcaptcha_config.user_id = mcaptcha_users.ID\n ORDER BY mcaptcha_config.config_id\n LIMIT ? OFFSET ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "avg_traffic",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "peak_sustainable_traffic",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "broke_my_site_traffic",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 400
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "captcha_key",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 400
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64"
|
||||
}
|
||||
@@ -12,11 +12,11 @@ authors = ["realaravinth <realaravinth@batsense.net>"]
|
||||
async-trait = "0.1.51"
|
||||
db-core = {path = "../db-core"}
|
||||
futures = "0.3.15"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "mysql", "time"] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "mysql", "time"] }
|
||||
uuid = { version = "1.4.0", features = ["v4", "serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "mysql", "time" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "mysql", "time" ] }
|
||||
db-core = {path = "../db-core", features = ["test"]}
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
|
||||
@@ -43,7 +43,6 @@ pub mod dev {
|
||||
pub use super::errors::*;
|
||||
pub use super::Database;
|
||||
pub use db_core::dev::*;
|
||||
pub use prelude::*;
|
||||
pub use sqlx::Error;
|
||||
}
|
||||
|
||||
@@ -1274,6 +1273,66 @@ impl MCDatabase for Database {
|
||||
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all easy captcha configurations on instance
|
||||
async fn get_all_easy_captchas(
|
||||
&self,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> DBResult<Vec<EasyCaptcha>> {
|
||||
struct InnerEasyCaptcha {
|
||||
captcha_key: String,
|
||||
name: String,
|
||||
username: String,
|
||||
peak_sustainable_traffic: i32,
|
||||
avg_traffic: i32,
|
||||
broke_my_site_traffic: Option<i32>,
|
||||
}
|
||||
let mut inner_res = sqlx::query_as!(
|
||||
InnerEasyCaptcha,
|
||||
"SELECT
|
||||
mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
|
||||
mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
|
||||
mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
|
||||
mcaptcha_config.name,
|
||||
mcaptcha_users.name as username,
|
||||
mcaptcha_config.captcha_key
|
||||
FROM
|
||||
mcaptcha_sitekey_user_provided_avg_traffic
|
||||
INNER JOIN
|
||||
mcaptcha_config
|
||||
ON
|
||||
mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
|
||||
INNER JOIN
|
||||
mcaptcha_users
|
||||
ON
|
||||
mcaptcha_config.user_id = mcaptcha_users.ID
|
||||
ORDER BY mcaptcha_config.config_id
|
||||
LIMIT ? OFFSET ?",
|
||||
limit as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||
let mut res = Vec::with_capacity(inner_res.len());
|
||||
inner_res.drain(0..).for_each(|v| {
|
||||
res.push(EasyCaptcha {
|
||||
key: v.captcha_key,
|
||||
description: v.name,
|
||||
username: v.username,
|
||||
traffic_pattern: TrafficPattern {
|
||||
broke_my_site_traffic: v
|
||||
.broke_my_site_traffic
|
||||
.as_ref()
|
||||
.map(|v| *v as u32),
|
||||
avg_traffic: v.avg_traffic as u32,
|
||||
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||
},
|
||||
})
|
||||
});
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT \n mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,\n mcaptcha_config.name,\n mcaptcha_users.name as username,\n mcaptcha_config.key\n FROM \n mcaptcha_sitekey_user_provided_avg_traffic \n INNER JOIN\n mcaptcha_config\n ON\n mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id\n INNER JOIN\n mcaptcha_users\n ON\n mcaptcha_config.user_id = mcaptcha_users.ID\n ORDER BY mcaptcha_config.config_id\n OFFSET $1 LIMIT $2; ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "avg_traffic",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "peak_sustainable_traffic",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "broke_my_site_traffic",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "name",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "username",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "key",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f01a9c09c8722bc195f477a8c3ce6466d415e7c74665fa882eff4a8566e70577"
|
||||
}
|
||||
@@ -12,11 +12,11 @@ authors = ["realaravinth <realaravinth@batsense.net>"]
|
||||
async-trait = "0.1.51"
|
||||
db-core = {path = "../db-core"}
|
||||
futures = "0.3.15"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "time" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "postgres", "time" ] }
|
||||
uuid = { version = "1.4.0", features = ["v4", "serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio-rustls", "postgres", "time"] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio-rustls", "postgres", "time"] }
|
||||
db-core = {path = "../db-core", features = ["test"]}
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
|
||||
@@ -43,7 +43,6 @@ pub mod dev {
|
||||
pub use super::errors::*;
|
||||
pub use super::Database;
|
||||
pub use db_core::dev::*;
|
||||
pub use prelude::*;
|
||||
pub use sqlx::Error;
|
||||
}
|
||||
|
||||
@@ -670,13 +669,8 @@ impl MCDatabase for Database {
|
||||
username: &str,
|
||||
captcha_key: &str,
|
||||
) -> DBResult<TrafficPattern> {
|
||||
struct Traffic {
|
||||
peak_sustainable_traffic: i32,
|
||||
avg_traffic: i32,
|
||||
broke_my_site_traffic: Option<i32>,
|
||||
}
|
||||
let res = sqlx::query_as!(
|
||||
Traffic,
|
||||
InnerTraffic,
|
||||
"SELECT
|
||||
avg_traffic,
|
||||
peak_sustainable_traffic,
|
||||
@@ -707,11 +701,67 @@ impl MCDatabase for Database {
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||
Ok(TrafficPattern {
|
||||
broke_my_site_traffic: res.broke_my_site_traffic.as_ref().map(|v| *v as u32),
|
||||
avg_traffic: res.avg_traffic as u32,
|
||||
peak_sustainable_traffic: res.peak_sustainable_traffic as u32,
|
||||
})
|
||||
Ok(res.into())
|
||||
}
|
||||
|
||||
/// Get all easy captcha configurations on instance
|
||||
async fn get_all_easy_captchas(
|
||||
&self,
|
||||
limit: usize,
|
||||
offset: usize,
|
||||
) -> DBResult<Vec<EasyCaptcha>> {
|
||||
struct InnerEasyCaptcha {
|
||||
key: String,
|
||||
peak_sustainable_traffic: i32,
|
||||
avg_traffic: i32,
|
||||
broke_my_site_traffic: Option<i32>,
|
||||
name: String,
|
||||
username: String,
|
||||
}
|
||||
let mut inner_res = sqlx::query_as!(
|
||||
InnerEasyCaptcha,
|
||||
"SELECT
|
||||
mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
|
||||
mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
|
||||
mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
|
||||
mcaptcha_config.name,
|
||||
mcaptcha_users.name as username,
|
||||
mcaptcha_config.key
|
||||
FROM
|
||||
mcaptcha_sitekey_user_provided_avg_traffic
|
||||
INNER JOIN
|
||||
mcaptcha_config
|
||||
ON
|
||||
mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
|
||||
INNER JOIN
|
||||
mcaptcha_users
|
||||
ON
|
||||
mcaptcha_config.user_id = mcaptcha_users.ID
|
||||
ORDER BY mcaptcha_config.config_id
|
||||
OFFSET $1 LIMIT $2; ",
|
||||
offset as i32,
|
||||
limit as i32
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||
let mut res = Vec::with_capacity(inner_res.len());
|
||||
inner_res.drain(0..).for_each(|v| {
|
||||
res.push(EasyCaptcha {
|
||||
key: v.key,
|
||||
description: v.name,
|
||||
username: v.username,
|
||||
traffic_pattern: TrafficPattern {
|
||||
broke_my_site_traffic: v
|
||||
.broke_my_site_traffic
|
||||
.as_ref()
|
||||
.map(|v| *v as u32),
|
||||
avg_traffic: v.avg_traffic as u32,
|
||||
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||
},
|
||||
})
|
||||
});
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Delete traffic configuration
|
||||
@@ -1346,3 +1396,19 @@ impl From<InternaleCaptchaConfig> for Captcha {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerTraffic {
|
||||
peak_sustainable_traffic: i32,
|
||||
avg_traffic: i32,
|
||||
broke_my_site_traffic: Option<i32>,
|
||||
}
|
||||
|
||||
impl From<InnerTraffic> for TrafficPattern {
|
||||
fn from(v: InnerTraffic) -> Self {
|
||||
TrafficPattern {
|
||||
broke_my_site_traffic: v.broke_my_site_traffic.as_ref().map(|v| *v as u32),
|
||||
avg_traffic: v.avg_traffic as u32,
|
||||
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,18 @@ services:
|
||||
image: mcaptcha/mcaptcha:latest
|
||||
ports:
|
||||
- 7000:7000
|
||||
environment:
|
||||
DATABASE_URL: postgres://postgres:password@mcaptcha_postgres:5432/postgres # set password at placeholder
|
||||
MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
|
||||
RUST_LOG: "debug"
|
||||
PORT: 7000
|
||||
env_file:
|
||||
- .env.docker-compose
|
||||
depends_on:
|
||||
- mcaptcha_postgres
|
||||
- mcaptcha_redis
|
||||
|
||||
mcaptcha_postgres:
|
||||
image: postgres:13.2
|
||||
image: postgres:18.0
|
||||
volumes:
|
||||
- mcaptcha-data:/var/lib/postgresql/
|
||||
- mcaptcha-data:/var/lib/postgresql
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password # change password
|
||||
PGDATA: /var/lib/postgresql/data/mcaptcha/
|
||||
|
||||
mcaptcha_redis:
|
||||
image: mcaptcha/cache:latest
|
||||
|
||||
@@ -56,23 +56,22 @@ you will be overriding the values set in the configuration files.
|
||||
|
||||
### Captcha
|
||||
|
||||
| Name | Value |
|
||||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --- |
|
||||
| `MCAPTCHA_captcha_SALT` | Salt has to be long and random |
|
||||
| `MCAPTCHA_captcha_GC` | Garbage collection duration in seconds, requires tuning but 30 is a good starting point |
|
||||
| `MCAPTCHA_captcha_RUNNERS` | [Performance] Number of runners to use for PoW validation. Defaults to number of CPUs available |
|
||||
| `MCAPTCHA_captcha_QUEUE_LENGTH` | [Performance] PoW Validation queue length, controls how many pending validation jobs can be held in queue |
|
||||
| `MCAPTCHA_captcha_ENABLE_STATS` | Record for CAPTCHA events like configuration fetch, solves and authentication of validation token. Useful for commercial deployments. | |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for traffic that took the website down |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration`% | Default duration to use in CAPTCHA configuration in easy mode |
|
||||
| Name | Value |
|
||||
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `MCAPTCHA_captcha_SALT` | Salt has to be long and random |
|
||||
| `MCAPTCHA_captcha_GC` | Garbage collection duration in seconds, requires tuning but 30 is a good starting point |
|
||||
| `MCAPTCHA_captcha_RUNNERS` | [Performance] Number of runners to use for PoW validation. Defaults to number of CPUs available |
|
||||
| `MCAPTCHA_captcha_QUEUE_LENGTH` | [Performance] PoW Validation queue length, controls how many pending validation jobs can be held in queue |
|
||||
| `MCAPTCHA_captcha_ENABLE_STATS` | Record for CAPTCHA events like configuration fetch, solves and authentication of validation token. Useful for commercial deployments. |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time` | This difficulty factor is used in to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time` | This difficulty factor is used in to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for traffic that took the website down |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time` | Default time (in seconds) to use to compute difficulty factor using stored PoW performance records. |
|
||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration` | Default duration to use in CAPTCHA configuration in easy mode |
|
||||
|
||||
\% See commits
|
||||
[`54b14291ec140e`](https://github.com/mCaptcha/mCaptcha/commit/54b14291ec140ea4cbbf73462d3d6fc2d39f2d2c)
|
||||
and
|
||||
[`42544ec421e0`](https://github.com/mCaptcha/mCaptcha/commit/42544ec421e0c3ec4a8d132e6101ab4069bf0065)
|
||||
for more info.
|
||||
See commits [`54b14291ec140e`](https://github.com/mCaptcha/mCaptcha/commit/54b14291ec140ea4cbbf73462d3d6fc2d39f2d2c) and [`42544ec421e0`](https://github.com/mCaptcha/mCaptcha/commit/42544ec421e0c3ec4a8d132e6101ab4069bf0065) for more info.
|
||||
|
||||
### SMTP
|
||||
|
||||
|
||||
2357
docs/openapi/package-lock.json
generated
2357
docs/openapi/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/mCaptcha/mCaptcha#readme",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "^1.4.0"
|
||||
"@redocly/cli": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
46
eslint.config.js
Normal file
46
eslint.config.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const {
|
||||
defineConfig,
|
||||
} = require("eslint/config");
|
||||
|
||||
const globals = require("globals");
|
||||
const tsParser = require("@typescript-eslint/parser");
|
||||
const typescriptEslint = require("@typescript-eslint/eslint-plugin");
|
||||
const js = require("@eslint/js");
|
||||
|
||||
const {
|
||||
FlatCompat,
|
||||
} = require("@eslint/eslintrc");
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
module.exports = defineConfig([{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
parserOptions: {},
|
||||
},
|
||||
|
||||
extends: compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
|
||||
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
},
|
||||
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
indent: ["error", 2],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
quotes: ["error", "double"],
|
||||
semi: ["error", "always"],
|
||||
},
|
||||
}]);
|
||||
19900
package-lock.json
generated
19900
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
41
package.json
41
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "vanilla",
|
||||
"main": "index.js",
|
||||
"version": "1.0.0",
|
||||
"license": "AGPL-3.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"lint": "yarn run eslint templates",
|
||||
@@ -10,35 +10,38 @@
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.6",
|
||||
"@types/jsdom": "^21.1.4",
|
||||
"@types/node": "^20.8.9",
|
||||
"@types/sinon": "^10.0.20",
|
||||
"@typescript-eslint/eslint-plugin": "^6.9.0",
|
||||
"@typescript-eslint/parser": "^6.9.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/jsdom": "^27.0.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/sinon": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"eslint": "^8.52.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"css-loader": "^7.0.0",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"globals": "^16.4.0",
|
||||
"jest": "^30.0.0",
|
||||
"jest-environment-jsdom": "^30.0.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jsdom": "^22.1.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"sass": "^1.69.5",
|
||||
"sass-loader": "^13.3.2",
|
||||
"sinon": "^17.0.0",
|
||||
"sass-loader": "^16.0.0",
|
||||
"sinon": "^21.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.5.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1"
|
||||
"webpack-cli": "^6.0.0",
|
||||
"webpack-dev-server": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-rc2",
|
||||
"@mcaptcha/vanilla-glue": "^0.1.0-rc1",
|
||||
"@mcaptcha/pow-wasm": "^0.1.0-rc2"
|
||||
"@mcaptcha/pow-wasm": "^0.1.0-rc2",
|
||||
"@mcaptcha/vanilla-glue": "^0.1.0-rc1"
|
||||
}
|
||||
}
|
||||
|
||||
21
renovate.json
Normal file
21
renovate.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":dependencyDashboard"
|
||||
],
|
||||
"labels": [
|
||||
"renovate-bot"
|
||||
],
|
||||
"prHourlyLimit": 0,
|
||||
"timezone": "Asia/kolkata",
|
||||
"prCreation": "immediate",
|
||||
"vulnerabilityAlerts": {
|
||||
"enabled": true,
|
||||
"labels": [
|
||||
"renovate-bot",
|
||||
"renovate-security",
|
||||
"security"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ is_ci(){
|
||||
|
||||
|
||||
|
||||
docker-compose down -v --remove-orphans || true
|
||||
docker-compose up -d
|
||||
docker compose down -v --remove-orphans || true
|
||||
docker compose up -d
|
||||
cd $(mktemp -d)
|
||||
pwd
|
||||
find
|
||||
@@ -40,4 +40,4 @@ else
|
||||
fi
|
||||
|
||||
cd $PROJECT_ROOT
|
||||
docker-compose down -v --remove-orphans || true
|
||||
docker compose down -v --remove-orphans || true
|
||||
|
||||
@@ -44,6 +44,7 @@ copy() {
|
||||
mkdir $TARGET_DIR/docs
|
||||
cp docs/DEPLOYMENT.md $TARGET_DIR/docs
|
||||
cp docs/CONFIGURATION.md $TARGET_DIR/docs
|
||||
cp config/default.toml $TARGET_DIR/config.toml
|
||||
|
||||
get_bin
|
||||
}
|
||||
|
||||
@@ -101,6 +101,79 @@ pub fn calculate(
|
||||
Ok(levels)
|
||||
}
|
||||
|
||||
async fn calculate_with_percentile(
|
||||
data: &AppData,
|
||||
tp: &TrafficPattern,
|
||||
) -> ServiceResult<Option<Vec<Level>>> {
|
||||
use crate::api::v1::stats::{percentile_bench_runner, PercentileReq};
|
||||
|
||||
let strategy = &data.settings.captcha.default_difficulty_strategy;
|
||||
|
||||
if strategy.avg_traffic_time.is_none()
|
||||
&& strategy.peak_sustainable_traffic_time.is_none()
|
||||
&& strategy.broke_my_site_traffic_time.is_none()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut req = PercentileReq {
|
||||
time: strategy.avg_traffic_time.unwrap(),
|
||||
percentile: 90.00,
|
||||
};
|
||||
let resp = percentile_bench_runner(data, &req).await?;
|
||||
if resp.difficulty_factor.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
let avg_traffic_difficulty = resp.difficulty_factor.unwrap();
|
||||
|
||||
req.time = strategy.peak_sustainable_traffic_time.unwrap();
|
||||
let resp = percentile_bench_runner(data, &req).await?;
|
||||
if resp.difficulty_factor.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
let peak_sustainable_traffic_difficulty = resp.difficulty_factor.unwrap();
|
||||
|
||||
req.time = strategy.broke_my_site_traffic_time.unwrap();
|
||||
let resp = percentile_bench_runner(data, &req).await?;
|
||||
let broke_my_site_traffic_difficulty = if resp.difficulty_factor.is_none() {
|
||||
resp.difficulty_factor.unwrap()
|
||||
} else {
|
||||
peak_sustainable_traffic_difficulty * 2
|
||||
};
|
||||
|
||||
let mut levels = vec![
|
||||
LevelBuilder::default()
|
||||
.difficulty_factor(avg_traffic_difficulty)?
|
||||
.visitor_threshold(tp.avg_traffic)
|
||||
.build()?,
|
||||
LevelBuilder::default()
|
||||
.difficulty_factor(peak_sustainable_traffic_difficulty)?
|
||||
.visitor_threshold(tp.peak_sustainable_traffic)
|
||||
.build()?,
|
||||
];
|
||||
let mut highest_level = LevelBuilder::default();
|
||||
highest_level.difficulty_factor(broke_my_site_traffic_difficulty)?;
|
||||
|
||||
match tp.broke_my_site_traffic {
|
||||
Some(broke_my_site_traffic) => {
|
||||
highest_level.visitor_threshold(broke_my_site_traffic)
|
||||
}
|
||||
None => match tp
|
||||
.peak_sustainable_traffic
|
||||
.checked_add(tp.peak_sustainable_traffic / 2)
|
||||
{
|
||||
Some(num) => highest_level.visitor_threshold(num),
|
||||
// TODO check for overflow: database saves these values as i32, so this u32 is cast
|
||||
// into i32. Should choose bigger number or casts properly
|
||||
None => highest_level.visitor_threshold(u32::MAX),
|
||||
},
|
||||
};
|
||||
|
||||
levels.push(highest_level.build()?);
|
||||
|
||||
Ok(Some(levels))
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.easy.create",
|
||||
wrap = "crate::api::v1::get_middleware()"
|
||||
@@ -113,8 +186,12 @@ async fn create(
|
||||
let username = id.identity().unwrap();
|
||||
let payload = payload.into_inner();
|
||||
let pattern = (&payload).into();
|
||||
let levels =
|
||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
||||
let levels = if let Some(levels) = calculate_with_percentile(&data, &pattern).await?
|
||||
{
|
||||
levels
|
||||
} else {
|
||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?
|
||||
};
|
||||
let msg = CreateCaptcha {
|
||||
levels,
|
||||
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
||||
@@ -147,6 +224,15 @@ async fn update(
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let payload = payload.into_inner();
|
||||
update_runner(&data, payload, username).await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
pub async fn update_runner(
|
||||
data: &AppData,
|
||||
payload: UpdateTrafficPattern,
|
||||
username: String,
|
||||
) -> ServiceResult<()> {
|
||||
let pattern = (&payload.pattern).into();
|
||||
let levels =
|
||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
||||
@@ -167,7 +253,7 @@ async fn update(
|
||||
.add_traffic_pattern(&username, &msg.key, &pattern)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -62,6 +62,7 @@ impl Health {
|
||||
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")]
|
||||
async fn health(data: AppData) -> impl Responder {
|
||||
let mut resp_builder = HealthBuilder::default();
|
||||
resp_builder.redis(None);
|
||||
|
||||
resp_builder.db(data.db.ping().await);
|
||||
|
||||
|
||||
@@ -32,82 +32,89 @@ pub mod routes {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get difficulty factor with max time limit for percentile of stats
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.stats.percentile_benches")]
|
||||
async fn percentile_benches(
|
||||
data: AppData,
|
||||
payload: web::Json<PercentileReq>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let count = data.db.stats_get_num_logs_under_time(payload.time).await?;
|
||||
pub async fn percentile_bench_runner(
|
||||
data: &AppData,
|
||||
req: &PercentileReq,
|
||||
) -> ServiceResult<PercentileResp> {
|
||||
let count = data.db.stats_get_num_logs_under_time(req.time).await?;
|
||||
|
||||
if count == 0 {
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
return Ok(PercentileResp {
|
||||
difficulty_factor: None,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if count < 2 {
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
return Ok(PercentileResp {
|
||||
difficulty_factor: None,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
let location = ((count - 1) as f64 * (payload.percentile / 100.00)) + 1.00;
|
||||
let location = ((count - 1) as f64 * (req.percentile / 100.00)) + 1.00;
|
||||
let fraction = location - location.floor();
|
||||
|
||||
if fraction > 0.00 {
|
||||
if let (Some(base), Some(ceiling)) = (
|
||||
data.db
|
||||
.stats_get_entry_at_location_for_time_limit_asc(
|
||||
payload.time,
|
||||
req.time,
|
||||
location.floor() as u32,
|
||||
)
|
||||
.await?,
|
||||
data.db
|
||||
.stats_get_entry_at_location_for_time_limit_asc(
|
||||
payload.time,
|
||||
req.time,
|
||||
location.floor() as u32 + 1,
|
||||
)
|
||||
.await?,
|
||||
) {
|
||||
let res = base as u32 + ((ceiling - base) as f64 * fraction).floor() as u32;
|
||||
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
return Ok(PercentileResp {
|
||||
difficulty_factor: Some(res),
|
||||
}));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if let Some(base) = data
|
||||
.db
|
||||
.stats_get_entry_at_location_for_time_limit_asc(
|
||||
payload.time,
|
||||
req.time,
|
||||
location.floor() as u32,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let res = base as u32;
|
||||
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
return Ok(PercentileResp {
|
||||
difficulty_factor: Some(res),
|
||||
}));
|
||||
});
|
||||
}
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
Ok(PercentileResp {
|
||||
difficulty_factor: None,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get difficulty factor with max time limit for percentile of stats
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.stats.percentile_benches")]
|
||||
async fn percentile_benches(
|
||||
data: AppData,
|
||||
payload: web::Json<PercentileReq>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
Ok(HttpResponse::Ok().json(percentile_bench_runner(&data, &payload).await?))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||
/// Health check return datatype
|
||||
pub struct PercentileReq {
|
||||
time: u32,
|
||||
percentile: f64,
|
||||
pub time: u32,
|
||||
pub percentile: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||
/// Health check return datatype
|
||||
pub struct PercentileResp {
|
||||
difficulty_factor: Option<u32>,
|
||||
pub difficulty_factor: Option<u32>,
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
@@ -188,7 +188,6 @@ impl Data {
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
/// create new instance of app data
|
||||
pub async fn new(s: &Settings, survey_secrets: SecretsStore) -> Arc<Self> {
|
||||
let creds = Self::get_creds();
|
||||
|
||||
50
src/demo.rs
50
src/demo.rs
@@ -8,6 +8,7 @@ use std::time::Duration;
|
||||
|
||||
use actix::clock::sleep;
|
||||
use actix::spawn;
|
||||
use tokio::sync::oneshot::{channel, error::TryRecvError, Receiver, Sender};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::api::v1::account::delete::runners::delete_user;
|
||||
@@ -23,20 +24,24 @@ pub const DEMO_USER: &str = "aaronsw";
|
||||
pub const DEMO_PASSWORD: &str = "password";
|
||||
|
||||
pub struct DemoUser {
|
||||
handle: JoinHandle<()>,
|
||||
tx: Sender<()>,
|
||||
}
|
||||
|
||||
impl DemoUser {
|
||||
pub async fn spawn(data: AppData, duration: Duration) -> ServiceResult<Self> {
|
||||
let handle = Self::run(data, duration).await?;
|
||||
let d = Self { handle };
|
||||
pub async fn spawn(
|
||||
data: AppData,
|
||||
duration: u32,
|
||||
) -> ServiceResult<(Self, JoinHandle<()>)> {
|
||||
let (tx, rx) = channel();
|
||||
let handle = Self::run(data, duration, rx).await?;
|
||||
let d = Self { tx };
|
||||
|
||||
Ok(d)
|
||||
Ok((d, handle))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn abort(&self) {
|
||||
self.handle.abort();
|
||||
pub fn abort(mut self) {
|
||||
self.tx.send(());
|
||||
}
|
||||
|
||||
/// register demo user runner
|
||||
@@ -71,16 +76,38 @@ impl DemoUser {
|
||||
|
||||
pub async fn run(
|
||||
data: AppData,
|
||||
duration: Duration,
|
||||
duration: u32,
|
||||
mut rx: Receiver<()>,
|
||||
) -> ServiceResult<JoinHandle<()>> {
|
||||
Self::register_demo_user(&data).await?;
|
||||
|
||||
fn can_run(rx: &mut Receiver<()>) -> bool {
|
||||
match rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
let mut exit = false;
|
||||
let fut = async move {
|
||||
loop {
|
||||
sleep(duration).await;
|
||||
if exit {
|
||||
break;
|
||||
}
|
||||
for _ in 0..duration {
|
||||
if can_run(&mut rx) {
|
||||
sleep(Duration::new(1, 0)).await;
|
||||
continue;
|
||||
} else {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = Self::delete_demo_user(&data).await {
|
||||
log::error!("Error while deleting demo user: {:?}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = Self::register_demo_user(&data).await {
|
||||
log::error!("Error while registering demo user: {:?}", e);
|
||||
}
|
||||
@@ -133,7 +160,7 @@ mod tests {
|
||||
assert!(!username_exists(&payload, &data).await.unwrap().exists);
|
||||
|
||||
// test the runner
|
||||
let user = DemoUser::spawn(data, duration).await.unwrap();
|
||||
let user = DemoUser::spawn(data, DURATION as u32).await.unwrap();
|
||||
let (_, signin_resp, token_key) =
|
||||
add_levels_util(data_inner, DEMO_USER, DEMO_PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
@@ -162,6 +189,7 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let res_levels: Vec<Level> = test::read_body_json(resp).await;
|
||||
assert!(res_levels.is_empty());
|
||||
user.abort();
|
||||
user.0.abort();
|
||||
user.1.await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
136
src/easy.rs
Normal file
136
src/easy.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (C) 2024// Copyright (C) 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use std::time::Duration;
|
||||
//use std::sync::atomicBool
|
||||
|
||||
use actix::clock::sleep;
|
||||
use actix::spawn;
|
||||
use tokio::sync::oneshot::{channel, error::TryRecvError, Receiver, Sender};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::api::v1::mcaptcha::easy::{
|
||||
update_runner, TrafficPatternRequest, UpdateTrafficPattern,
|
||||
};
|
||||
use crate::*;
|
||||
|
||||
use errors::*;
|
||||
|
||||
pub struct UpdateEasyCaptcha {
|
||||
tx: Sender<()>,
|
||||
}
|
||||
|
||||
impl UpdateEasyCaptcha {
|
||||
pub async fn spawn(
|
||||
data: AppData,
|
||||
duration: u32,
|
||||
) -> ServiceResult<(Self, JoinHandle<()>)> {
|
||||
let (tx, rx) = channel();
|
||||
let handle = Self::run(data, duration, rx).await?;
|
||||
let d = Self { tx };
|
||||
|
||||
Ok((d, handle))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn abort(mut self) {
|
||||
self.tx.send(());
|
||||
}
|
||||
|
||||
/// update configurations
|
||||
async fn update_captcha_configurations(
|
||||
data: &AppData,
|
||||
rx: &mut Receiver<()>,
|
||||
) -> ServiceResult<()> {
|
||||
let limit = 10;
|
||||
let mut offset = 0;
|
||||
let mut page = 0;
|
||||
loop {
|
||||
offset = page * limit;
|
||||
|
||||
if !Self::can_run(rx) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut patterns = data.db.get_all_easy_captchas(limit, offset).await?;
|
||||
if patterns.is_empty() {
|
||||
break;
|
||||
}
|
||||
for pattern in patterns.drain(0..) {
|
||||
if !Self::can_run(rx) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let publish_benchmarks =
|
||||
data.db.analytics_captcha_is_published(&pattern.key).await?;
|
||||
|
||||
let req = UpdateTrafficPattern {
|
||||
pattern: TrafficPatternRequest {
|
||||
avg_traffic: pattern.traffic_pattern.avg_traffic,
|
||||
peak_sustainable_traffic: pattern
|
||||
.traffic_pattern
|
||||
.peak_sustainable_traffic,
|
||||
broke_my_site_traffic: pattern
|
||||
.traffic_pattern
|
||||
.broke_my_site_traffic,
|
||||
description: pattern.description,
|
||||
publish_benchmarks,
|
||||
},
|
||||
key: pattern.key,
|
||||
};
|
||||
if !Self::can_run(rx) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
update_runner(&data, req, pattern.username).await?;
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn can_run(rx: &mut Receiver<()>) -> bool {
|
||||
match rx.try_recv() {
|
||||
Err(TryRecvError::Empty) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
data: AppData,
|
||||
duration: u32,
|
||||
mut rx: Receiver<()>,
|
||||
) -> ServiceResult<JoinHandle<()>> {
|
||||
let mut exit = false;
|
||||
let fut = async move {
|
||||
loop {
|
||||
if exit {
|
||||
break;
|
||||
}
|
||||
for _ in 0..duration {
|
||||
if Self::can_run(&mut rx) {
|
||||
sleep(Duration::new(1, 0)).await;
|
||||
continue;
|
||||
} else {
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(err) = Self::update_captcha_configurations(&data, &mut rx)
|
||||
.await
|
||||
.err()
|
||||
{
|
||||
log::error!(
|
||||
"Tried to update easy captcha configurations in background {:?}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
let handle = spawn(fut);
|
||||
Ok(handle)
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ impl std::cmp::PartialEq for SmtpErrorWrapper {
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, PartialEq, Error)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub enum ServiceError {
|
||||
#[display(fmt = "internal server error")]
|
||||
InternalServerError,
|
||||
@@ -114,14 +113,11 @@ pub enum ServiceError {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub struct ErrorToResponse {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl ResponseError for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponseBuilder::new(self.status_code())
|
||||
.append_header((header::CONTENT_TYPE, "application/json; charset=UTF-8"))
|
||||
@@ -133,7 +129,6 @@ impl ResponseError for ServiceError {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
ServiceError::ClosedForRegistration => StatusCode::FORBIDDEN,
|
||||
@@ -177,7 +172,6 @@ impl ResponseError for ServiceError {
|
||||
}
|
||||
|
||||
impl From<CredsError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: CredsError) -> ServiceError {
|
||||
match e {
|
||||
CredsError::UsernameCaseMappedError => ServiceError::UsernameCaseMappedError,
|
||||
@@ -192,7 +186,6 @@ impl From<CredsError> for ServiceError {
|
||||
}
|
||||
|
||||
impl From<DBError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: DBError) -> ServiceError {
|
||||
println!("from conversin: {}", e);
|
||||
match e {
|
||||
@@ -208,57 +201,46 @@ impl From<DBError> for ServiceError {
|
||||
}
|
||||
|
||||
impl From<ValidationErrors> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(_: ValidationErrors) -> ServiceError {
|
||||
ServiceError::NotAnEmail
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(_: ParseError) -> ServiceError {
|
||||
ServiceError::NotAUrl
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl From<CaptchaError> for ServiceError {
|
||||
fn from(e: CaptchaError) -> ServiceError {
|
||||
ServiceError::CaptchaError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl From<SmtpError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: SmtpError) -> Self {
|
||||
ServiceError::UnableToSendEmail(SmtpErrorWrapper(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl From<RecvError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: RecvError) -> Self {
|
||||
log::error!("{:?}", e);
|
||||
ServiceError::InternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl From<MailboxError> for ServiceError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: MailboxError) -> Self {
|
||||
log::error!("{:?}", e);
|
||||
ServiceError::InternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub type ServiceResult<V> = std::result::Result<V, ServiceError>;
|
||||
|
||||
#[derive(Debug, Display, PartialEq, Error)]
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub enum PageError {
|
||||
#[display(fmt = "Something weng wrong: Internal server error")]
|
||||
InternalServerError,
|
||||
@@ -267,17 +249,13 @@ pub enum PageError {
|
||||
ServiceError(ServiceError),
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl From<ServiceError> for PageError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: ServiceError) -> Self {
|
||||
PageError::ServiceError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl From<DBError> for PageError {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn from(e: DBError) -> Self {
|
||||
let se: ServiceError = e.into();
|
||||
se.into()
|
||||
@@ -297,7 +275,6 @@ impl ResponseError for PageError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
PageError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
@@ -306,7 +283,6 @@ impl ResponseError for PageError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub type PageResult<V> = std::result::Result<V, PageError>;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
30
src/main.rs
30
src/main.rs
@@ -14,6 +14,7 @@ use actix_web::{
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
mod api;
|
||||
mod data;
|
||||
@@ -21,6 +22,7 @@ mod date;
|
||||
mod db;
|
||||
mod demo;
|
||||
mod docs;
|
||||
mod easy;
|
||||
mod email;
|
||||
mod errors;
|
||||
#[macro_use]
|
||||
@@ -90,7 +92,6 @@ pub const CACHE_AGE: u32 = 604800;
|
||||
pub type ArcData = Arc<crate::data::Data>;
|
||||
pub type AppData = actix_web::web::Data<ArcData>;
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
use std::time::Duration;
|
||||
@@ -110,11 +111,22 @@ async fn main() -> std::io::Result<()> {
|
||||
let data = Data::new(&settings, secrets.clone()).await;
|
||||
let data = actix_web::web::Data::new(data);
|
||||
|
||||
let mut demo_user: Option<DemoUser> = None;
|
||||
let mut demo_user: Option<(DemoUser, JoinHandle<()>)> = None;
|
||||
|
||||
if settings.allow_demo && settings.allow_registration {
|
||||
demo_user = Some(
|
||||
DemoUser::spawn(data.clone(), Duration::from_secs(60 * 30))
|
||||
demo_user = Some(DemoUser::spawn(data.clone(), 60 * 30).await.unwrap());
|
||||
}
|
||||
|
||||
let mut update_easy_captcha: Option<(easy::UpdateEasyCaptcha, JoinHandle<()>)> =
|
||||
None;
|
||||
if settings
|
||||
.captcha
|
||||
.default_difficulty_strategy
|
||||
.avg_traffic_time
|
||||
.is_some()
|
||||
{
|
||||
update_easy_captcha = Some(
|
||||
easy::UpdateEasyCaptcha::spawn(data.clone(), 60 * 30)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
@@ -156,7 +168,13 @@ async fn main() -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
if let Some(demo_user) = demo_user {
|
||||
demo_user.abort();
|
||||
demo_user.0.abort();
|
||||
demo_user.1.await.unwrap();
|
||||
}
|
||||
|
||||
if let Some(update_easy_captcha) = update_easy_captcha {
|
||||
update_easy_captcha.0.abort();
|
||||
update_easy_captcha.1.await.unwrap();
|
||||
}
|
||||
|
||||
if let Some(survey_upload_handle) = survey_upload_handle {
|
||||
@@ -166,7 +184,6 @@ async fn main() -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub fn get_json_err() -> JsonConfig {
|
||||
JsonConfig::default().error_handler(|err, _| {
|
||||
//debug!("JSON deserialization error: {:?}", &err);
|
||||
@@ -174,7 +191,6 @@ pub fn get_json_err() -> JsonConfig {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub fn get_identity_service(
|
||||
settings: &Settings,
|
||||
) -> IdentityService<CookieIdentityPolicy> {
|
||||
|
||||
@@ -25,7 +25,6 @@ pub fn get_middleware() -> Authentication<routes::Routes> {
|
||||
Authentication::with_identity(routes::ROUTES)
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
|
||||
@@ -10,6 +10,7 @@ use sailfish::TemplateOnce;
|
||||
mod notifications;
|
||||
mod settings;
|
||||
pub mod sitekey;
|
||||
mod utils;
|
||||
|
||||
use db_core::Captcha;
|
||||
|
||||
@@ -47,18 +48,21 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(panel);
|
||||
settings::services(cfg);
|
||||
sitekey::services(cfg);
|
||||
utils::services(cfg);
|
||||
cfg.service(notifications::notifications);
|
||||
}
|
||||
|
||||
pub mod routes {
|
||||
use super::settings::routes::Settings;
|
||||
use super::sitekey::routes::Sitekey;
|
||||
use super::utils::routes::Utils;
|
||||
|
||||
pub struct Panel {
|
||||
pub home: &'static str,
|
||||
pub sitekey: Sitekey,
|
||||
pub notifications: &'static str,
|
||||
pub settings: Settings,
|
||||
pub utils: Utils,
|
||||
}
|
||||
|
||||
impl Panel {
|
||||
@@ -68,10 +72,11 @@ pub mod routes {
|
||||
sitekey: Sitekey::new(),
|
||||
notifications: "/notifications",
|
||||
settings: Settings::new(),
|
||||
utils: Utils::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_sitemap() -> [&'static str; 5] {
|
||||
pub const fn get_sitemap() -> [&'static str; 6] {
|
||||
const PANEL: Panel = Panel::new();
|
||||
const S: [&str; 2] = Sitekey::get_sitemap();
|
||||
|
||||
@@ -81,6 +86,7 @@ pub mod routes {
|
||||
S[0],
|
||||
S[1],
|
||||
Settings::get_sitemap()[0],
|
||||
Utils::get_sitemap()[0],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
280
src/pages/panel/utils.rs
Normal file
280
src/pages/panel/utils.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
// Copyright (C) 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
use crate::api::v1::stats::{percentile_bench_runner, PercentileReq, PercentileResp};
|
||||
use crate::errors::PageResult;
|
||||
use crate::pages::auth::sudo::SudoPage;
|
||||
use crate::AppData;
|
||||
|
||||
pub mod routes {
|
||||
pub struct Utils {
|
||||
pub percentile: &'static str,
|
||||
}
|
||||
|
||||
impl Utils {
|
||||
pub const fn new() -> Self {
|
||||
Utils {
|
||||
percentile: "/utils/percentile",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_sitemap() -> [&'static str; 1] {
|
||||
const S: Utils = Utils::new();
|
||||
[S.percentile]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_percentile);
|
||||
cfg.service(post_percentile);
|
||||
}
|
||||
|
||||
const PAGE: &str = "Difficulty factor statistics";
|
||||
|
||||
#[derive(TemplateOnce, Clone)]
|
||||
#[template(path = "panel/utils/percentile/index.html")]
|
||||
pub struct PercentilePage {
|
||||
time: Option<u32>,
|
||||
percentile: Option<f64>,
|
||||
difficulty_factor: Option<u32>,
|
||||
}
|
||||
|
||||
#[my_codegen::get(
|
||||
path = "crate::PAGES.panel.utils.percentile",
|
||||
wrap = "crate::pages::get_middleware()"
|
||||
)]
|
||||
async fn get_percentile(id: Identity) -> PageResult<impl Responder> {
|
||||
let data = PercentilePage {
|
||||
time: None,
|
||||
percentile: None,
|
||||
difficulty_factor: None,
|
||||
};
|
||||
|
||||
let body = data.render_once().unwrap();
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(body))
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::PAGES.panel.utils.percentile",
|
||||
wrap = "crate::pages::get_middleware()"
|
||||
)]
|
||||
async fn post_percentile(
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
payload: web::Form<PercentileReq>,
|
||||
) -> PageResult<impl Responder> {
|
||||
let resp = percentile_bench_runner(&data, &payload).await?;
|
||||
let page = PercentilePage {
|
||||
time: Some(payload.time),
|
||||
percentile: Some(payload.percentile),
|
||||
difficulty_factor: resp.difficulty_factor,
|
||||
};
|
||||
|
||||
let body = page.render_once().unwrap();
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(body))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::{http::StatusCode, test, web::Bytes, App};
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::services;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn page_stats_bench_work_pg() {
|
||||
let data = crate::tests::pg::get_data().await;
|
||||
page_stats_bench_work(data).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn page_stats_bench_work_maria() {
|
||||
let data = crate::tests::maria::get_data().await;
|
||||
page_stats_bench_work(data).await;
|
||||
}
|
||||
|
||||
async fn page_stats_bench_work(data: ArcData) {
|
||||
use crate::tests::*;
|
||||
|
||||
const NAME: &str = "pagebenchstatsuesr";
|
||||
const EMAIL: &str = "pagebenchstatsuesr@testadminuser.com";
|
||||
const PASSWORD: &str = "longpassword2";
|
||||
|
||||
const DEVICE_USER_PROVIDED: &str = "foo";
|
||||
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
|
||||
const THREADS: i32 = 4;
|
||||
|
||||
let data = &data;
|
||||
{
|
||||
delete_user(&data, NAME).await;
|
||||
}
|
||||
|
||||
register_and_signin(data, NAME, EMAIL, PASSWORD).await;
|
||||
// create captcha
|
||||
let (_, signin_resp, key) = add_levels_util(data, NAME, PASSWORD).await;
|
||||
let app = get_app!(data).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
|
||||
let page = 1;
|
||||
let tmp_id = uuid::Uuid::new_v4();
|
||||
let download_rotue = V1_API_ROUTES
|
||||
.survey
|
||||
.get_download_route(&tmp_id.to_string(), page);
|
||||
|
||||
let download_req = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get().uri(&download_rotue).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(download_req.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
data.db
|
||||
.analytics_create_psuedo_id_if_not_exists(&key.key)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let psuedo_id = data
|
||||
.db
|
||||
.analytics_get_psuedo_id_from_capmaign_id(&key.key)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for i in 1..6 {
|
||||
println!("[{i}] Saving analytics");
|
||||
let analytics = db_core::CreatePerformanceAnalytics {
|
||||
time: i,
|
||||
difficulty_factor: i,
|
||||
worker_type: "wasm".into(),
|
||||
};
|
||||
data.db.analysis_save(&key.key, &analytics).await.unwrap();
|
||||
}
|
||||
|
||||
let msg = PercentileReq {
|
||||
time: 1,
|
||||
percentile: 99.00,
|
||||
};
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||
|
||||
assert!(resp.difficulty_factor.is_none());
|
||||
|
||||
let msg = PercentileReq {
|
||||
time: 1,
|
||||
percentile: 100.00,
|
||||
};
|
||||
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||
|
||||
// start
|
||||
let percentile_resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get()
|
||||
.uri(&crate::PAGES.panel.utils.percentile)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||
|
||||
let body: Bytes = test::read_body(percentile_resp).await;
|
||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||
|
||||
assert!(body.contains("Maximum time taken"));
|
||||
|
||||
let percentile_resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get()
|
||||
.uri(&crate::PAGES.panel.utils.percentile)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||
|
||||
let body: Bytes = test::read_body(percentile_resp).await;
|
||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||
|
||||
assert!(body.contains("Maximum time taken"));
|
||||
|
||||
// end
|
||||
// start post
|
||||
|
||||
let msg = PercentileReq {
|
||||
time: 1,
|
||||
percentile: 99.00,
|
||||
};
|
||||
|
||||
let percentile_resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::post()
|
||||
.uri(&crate::PAGES.panel.utils.percentile)
|
||||
.set_form(&msg)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||
|
||||
let body: Bytes = test::read_body(percentile_resp).await;
|
||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||
|
||||
assert!(body.contains(
|
||||
"Not enough inputs to compute statistics. Please try again later"
|
||||
));
|
||||
assert!(body.contains(&1.to_string()));
|
||||
assert!(body.contains(&99.00.to_string()));
|
||||
// end post
|
||||
|
||||
// start post
|
||||
|
||||
let msg = PercentileReq {
|
||||
time: 2,
|
||||
percentile: 100.00,
|
||||
};
|
||||
|
||||
let percentile_resp = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::post()
|
||||
.uri(&crate::PAGES.panel.utils.percentile)
|
||||
.set_form(&msg)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||
|
||||
let body: Bytes = test::read_body(percentile_resp).await;
|
||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||
|
||||
assert!(body.contains("Difficulty factor: 2"));
|
||||
assert!(body.contains(&2.to_string()));
|
||||
assert!(body.contains(&100.00.to_string()));
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,11 @@ pub struct Captcha {
|
||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
pub struct DefaultDifficultyStrategy {
|
||||
pub avg_traffic_difficulty: u32,
|
||||
pub broke_my_site_traffic_difficulty: u32,
|
||||
pub avg_traffic_time: Option<u32>,
|
||||
pub peak_sustainable_traffic_difficulty: u32,
|
||||
pub peak_sustainable_traffic_time: Option<u32>,
|
||||
pub broke_my_site_traffic_time: Option<u32>,
|
||||
pub broke_my_site_traffic_difficulty: u32,
|
||||
pub duration: u32,
|
||||
}
|
||||
|
||||
@@ -53,7 +56,6 @@ pub struct Smtp {
|
||||
}
|
||||
|
||||
impl Server {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
pub fn get_ip(&self) -> String {
|
||||
format!("{}:{}", self.ip, self.port)
|
||||
}
|
||||
@@ -113,7 +115,7 @@ pub struct Settings {
|
||||
pub smtp: Option<Smtp>,
|
||||
}
|
||||
|
||||
const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
||||
const ENV_VAR_CONFIG: [(&str, &str); 32] = [
|
||||
/* top-level */
|
||||
("debug", "MCAPTCHA_debug"),
|
||||
("commercial", "MCAPTCHA_commercial"),
|
||||
@@ -150,6 +152,9 @@ const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
||||
( "captcha.default_difficulty_strategy.duration",
|
||||
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
|
||||
),
|
||||
("captcha.default_difficulty_strategy.avg_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time"),
|
||||
("captcha.default_difficulty_strategy.peak_sustainable_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time"),
|
||||
("captcha.default_difficulty_strategy.broke_my_site_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time"),
|
||||
|
||||
|
||||
/* SMTP */
|
||||
@@ -199,7 +204,6 @@ const DEPRECATED_ENV_VARS: [(&str, &str); 23] = [
|
||||
("smtp.port", "MCAPTCHA_SMTP_PORT"),
|
||||
];
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Settings {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let mut s = Config::builder();
|
||||
@@ -251,6 +255,28 @@ impl Settings {
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
fn check_easy_captcha_config(&self) {
|
||||
let s = &self.captcha.default_difficulty_strategy;
|
||||
if s.avg_traffic_time.is_some() {
|
||||
if s.broke_my_site_traffic_time.is_none()
|
||||
|| s.peak_sustainable_traffic_time.is_none()
|
||||
{
|
||||
panic!("if captcha.default_difficulty_strategy.avg_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.peak_sustainable_traffic_time must also be set");
|
||||
}
|
||||
}
|
||||
if s.peak_sustainable_traffic_time.is_some() {
|
||||
if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
|
||||
{
|
||||
panic!("if captcha.default_difficulty_strategy.peak_sustainable_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
|
||||
}
|
||||
}
|
||||
if s.broke_my_site_traffic_time.is_some() {
|
||||
if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
|
||||
{
|
||||
panic!("if captcha.default_difficulty_strategy.broke_my_site_traffic_time is set, then captcha.default_difficulty_strategy.peak_sustainable_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||
for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
|
||||
@@ -538,6 +564,30 @@ mod tests {
|
||||
999,
|
||||
captcha.default_difficulty_strategy.duration
|
||||
);
|
||||
helper!(
|
||||
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time",
|
||||
"10",
|
||||
Some(10),
|
||||
captcha.default_difficulty_strategy.avg_traffic_time
|
||||
);
|
||||
|
||||
helper!(
|
||||
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time",
|
||||
"20",
|
||||
Some(20),
|
||||
captcha
|
||||
.default_difficulty_strategy
|
||||
.peak_sustainable_traffic_time
|
||||
);
|
||||
|
||||
helper!(
|
||||
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time",
|
||||
"30",
|
||||
Some(30),
|
||||
captcha
|
||||
.default_difficulty_strategy
|
||||
.broke_my_site_traffic_time
|
||||
);
|
||||
|
||||
/* SMTP */
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"use strict";
|
||||
|
||||
import createError from "./index";
|
||||
import * as e from "./index";
|
||||
|
||||
import setup from "./setUpTests";
|
||||
|
||||
"use strict";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<nav class="secondary-menu">
|
||||
<input type="checkbox" class="nav-toggle" id="nav-toggle" >
|
||||
<input type="checkbox" class="nav-toggle" id="nav-toggle" >
|
||||
<div class="secondary-menu__heading">
|
||||
<a class="novisit" href="/">
|
||||
<img class="secondary-menu__logo" src="<.= crate::MCAPTCHA_TRANS_ICON.0 .>" alt="<.= crate::MCAPTCHA_TRANS_ICON.1 .>" />
|
||||
<a class="novisit" href="/">
|
||||
<img class="secondary-menu__logo" src="<.= crate::MCAPTCHA_TRANS_ICON.0 .>" alt="<.= crate::MCAPTCHA_TRANS_ICON.1 .>" />
|
||||
</a>
|
||||
<a href="/" class="secondary-menu__brand-name">
|
||||
mCaptcha
|
||||
</a>
|
||||
<label class="nav__hamburger-menu"for="nav-toggle">
|
||||
<label class="nav__hamburger-menu"for="nav-toggle">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
</div>
|
||||
<ul class="secondary-menu__list">
|
||||
<li class="secondary-menu__item">
|
||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.home .>">
|
||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.home .>">
|
||||
<img class="secondary-menu__icon" src="<.= crate::HOME.0 .>" alt="<.= crate::HOME.1 .>" />
|
||||
<div class="secondary-menu__item-name">
|
||||
Overview
|
||||
@@ -29,13 +29,21 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
</a>
|
||||
</li>
|
||||
<li class="secondary-menu__item">
|
||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.sitekey.list .>">
|
||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.sitekey.list .>">
|
||||
<img class="secondary-menu__icon" src="<.= crate::KEY.0 .>" alt="<.= crate::KEY.1 .>" />
|
||||
<div class="secondary-menu__item-name">
|
||||
Site Keys
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="secondary-menu__item">
|
||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.utils.percentile .>">
|
||||
<img class="secondary-menu__icon" src="<.= crate::BAR_CHART.0 .>" alt="<.= crate::BAR_CHART.1 .>" />
|
||||
<div class="secondary-menu__item-name">
|
||||
Statistics
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="secondary-menu__item">
|
||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.settings.home .>">
|
||||
<img class="secondary-menu__icon" src="<.= crate::SETTINGS_ICON.0 .>" alt="<.= crate::SETTINGS_ICON.1 .>" />
|
||||
|
||||
67
templates/panel/utils/percentile/index.html
Normal file
67
templates/panel/utils/percentile/index.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<. include!("../../../components/headers/index.html"); .>
|
||||
<. include!("../../navbar/index.html"); .>
|
||||
<div class="tmp-layout">
|
||||
<. include!("../../header/index.html"); .>
|
||||
<main class="panel-main">
|
||||
<. include!("../../help-banner/index.html"); .>
|
||||
<!-- Main content container -->
|
||||
<div class="inner-container">
|
||||
<div class="sitekey-form" action="<.= crate::V1_API_ROUTES.captcha.create .>" method="post">
|
||||
<h1 class="form__title">
|
||||
<.= PAGE .>
|
||||
</h1>
|
||||
|
||||
<form class="settings__form" id="utils_percentile-form"
|
||||
action="<.= crate::PAGES.panel.utils.percentile .>" method="post">
|
||||
|
||||
<. if let Some(difficulty_factor) = difficulty_factor { .>
|
||||
<legend class="sitekey__level-title">
|
||||
<p>Difficulty factor: <.= difficulty_factor .></p>
|
||||
</legend>
|
||||
<. } else { .>
|
||||
<. if time.is_some() && percentile.is_some() { .>
|
||||
<legend class="sitekey__level-title">
|
||||
<p>Not enough inputs to compute statistics. Please try again later</p>
|
||||
</legend>
|
||||
<. } .>
|
||||
<. } .>
|
||||
|
||||
|
||||
<label class="settings-form__label" for="time">
|
||||
Maximum time taken to solve CAPTCHA (in seconds)
|
||||
<input
|
||||
class="settings-form__input"
|
||||
type="number"
|
||||
name="time"
|
||||
required
|
||||
id="time"
|
||||
<. if let Some(time) = time { .>
|
||||
value="<.= time .>"
|
||||
<. } .>
|
||||
/>
|
||||
</label>
|
||||
<label class="settings-form__label" for="percentile">
|
||||
Percentile of requests coming under time limit
|
||||
<input
|
||||
class="settings-form__input"
|
||||
type="number"
|
||||
name="percentile"
|
||||
required
|
||||
id="percentile"
|
||||
<. if let Some(percentile) = percentile { .>
|
||||
value="<.= percentile .>"
|
||||
<. } .>
|
||||
/>
|
||||
</label>
|
||||
<button class="settings__submit-btn" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of container -->
|
||||
<. include!("../../../components/footers.html"); .>
|
||||
@@ -2,10 +2,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"use strict";
|
||||
|
||||
import {Router} from "./router";
|
||||
|
||||
"use strict";
|
||||
|
||||
const result = {
|
||||
result: "",
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"use strict";
|
||||
|
||||
import genJsonPayload from "./genJsonPayload";
|
||||
|
||||
"use strict";
|
||||
|
||||
const payload = {
|
||||
username: "Jhon",
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"use strict";
|
||||
|
||||
import getFormUrl from "./getFormUrl";
|
||||
import {getLoginFormHtml} from "../setUpTests";
|
||||
|
||||
"use strict";
|
||||
|
||||
const formClassName = "form__box";
|
||||
const formURL = "/api/v1/signin";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"use strict";
|
||||
|
||||
import isBlankString from "./isBlankString";
|
||||
import {mockAlert} from "../setUpTests";
|
||||
@@ -9,7 +10,6 @@ import {mockAlert} from "../setUpTests";
|
||||
|
||||
import setup from "../components/error/setUpTests";
|
||||
|
||||
"use strict";
|
||||
|
||||
mockAlert();
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"use strict";
|
||||
|
||||
import isNumber from "./isNumber";
|
||||
|
||||
"use strict";
|
||||
|
||||
it("getFromUrl workds", () => {
|
||||
expect(isNumber("test")).toBe(false);
|
||||
expect(isNumber("1test213")).toBe(false);
|
||||
|
||||
@@ -56,53 +56,41 @@ type messageTextReturn = {
|
||||
error: () => void;
|
||||
};
|
||||
|
||||
export const messageText = (): messageTextReturn => {
|
||||
const beforeID = "widget__verification-text--before";
|
||||
const duringID = "widget__verification-text--during";
|
||||
const errorID = "widget__verification-text--error";
|
||||
const afterID = "widget__verification-text--after";
|
||||
export const BEFORE = "I'm not a robot";
|
||||
export const DURING = "Processing...";
|
||||
export const AFTER = "Verified!";
|
||||
export const ERROR = "Something went wrong";
|
||||
|
||||
const before = new LazyElement(beforeID);
|
||||
const after = new LazyElement(afterID);
|
||||
const during = new LazyElement(duringID);
|
||||
const error = new LazyElement(errorID);
|
||||
export const messageText = (): messageTextReturn => {
|
||||
const conatinerID = "widget__verification-text";
|
||||
|
||||
const container = new LazyElement(conatinerID);
|
||||
|
||||
/** runner fn to display HTMLElement **/
|
||||
const showMsg = (e: HTMLElement) => (e.style.display = "block");
|
||||
/** runner fn to hide HTMLElement **/
|
||||
const hideMsg = (e: HTMLElement) => (e.style.display = "none");
|
||||
const showMsg = (value: string) => {
|
||||
container.get().innerText = value;
|
||||
btn().ariaValueText = value;
|
||||
};
|
||||
|
||||
return {
|
||||
/** display "before" message **/
|
||||
before: () => {
|
||||
showMsg(before.get());
|
||||
hideMsg(after.get());
|
||||
hideMsg(during.get());
|
||||
hideMsg(error.get());
|
||||
showMsg(BEFORE);
|
||||
},
|
||||
|
||||
/** display "after" message **/
|
||||
after: () => {
|
||||
hideMsg(before.get());
|
||||
showMsg(after.get());
|
||||
hideMsg(during.get());
|
||||
hideMsg(error.get());
|
||||
showMsg(AFTER);
|
||||
},
|
||||
|
||||
/** display "during" message **/
|
||||
during: () => {
|
||||
hideMsg(before.get());
|
||||
hideMsg(after.get());
|
||||
showMsg(during.get());
|
||||
hideMsg(error.get());
|
||||
showMsg(DURING);
|
||||
},
|
||||
|
||||
/** display "error" message **/
|
||||
error: () => {
|
||||
hideMsg(before.get());
|
||||
hideMsg(after.get());
|
||||
hideMsg(during.get());
|
||||
showMsg(error.get());
|
||||
showMsg(ERROR);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,14 +19,15 @@ SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
</div>
|
||||
</noscript>
|
||||
<label class="widget__verification-container" for="widget__verification-checkbox">
|
||||
<input
|
||||
<span id="widget__verification-text"
|
||||
>I'm not a robot</span>
|
||||
<input disabled
|
||||
id="widget__verification-checkbox"
|
||||
aria-valuenow="I'm not a robot"
|
||||
aria-checked="false"
|
||||
role="checkbox"
|
||||
class="widget__verification-checkbox"
|
||||
type="checkbox" />
|
||||
<span id="widget__verification-text--before">I'm not a robot</span>
|
||||
<span id="widget__verification-text--during">Processing...</span>
|
||||
<span id="widget__verification-text--after">Verified!</span>
|
||||
<span id="widget__verification-text--error">Something went wrong</span>
|
||||
</label>
|
||||
<div class="widget__mcaptcha-details">
|
||||
<a href="<.= crate::PKG_HOMEPAGE .>"
|
||||
@@ -54,6 +55,8 @@ SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="progress__bar"><div class="progress__fill"></div></div>
|
||||
<div class="progress__bar"><div
|
||||
aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
|
||||
role="progressbar" class="progress__fill"></div></div>
|
||||
</main>
|
||||
<.include!("./footer.html"); .>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
import { Work, ServiceWorkerMessage } from "./types";
|
||||
import {Work, ServiceWorkerMessage} from "./types";
|
||||
import fetchPoWConfig from "./fetchPoWConfig";
|
||||
import sendWork from "./sendWork";
|
||||
import sendToParent from "./sendToParent";
|
||||
@@ -12,7 +12,17 @@ import * as CONST from "./const";
|
||||
import "./main.scss";
|
||||
|
||||
let LOCK = false;
|
||||
const worker = new Worker("/bench.js");
|
||||
|
||||
const workerPromise = new Promise<Worker>((res) => {
|
||||
const worker = new Worker("/bench.js");
|
||||
worker.onmessage = (event: MessageEvent) => {
|
||||
const message: ServiceWorkerMessage = event.data;
|
||||
if(message.type === "ready") {
|
||||
console.log("worker ready");
|
||||
res(worker);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/** add mcaptcha widget element to DOM */
|
||||
export const registerVerificationEventHandler = (): void => {
|
||||
@@ -20,11 +30,21 @@ export const registerVerificationEventHandler = (): void => {
|
||||
document.querySelector(".widget__verification-container")
|
||||
);
|
||||
verificationContainer.style.display = "flex";
|
||||
CONST.btn().addEventListener("click", (e) => solveCaptchaRunner(e));
|
||||
workerPromise.then((worker: Worker) => {
|
||||
const btn = CONST.btn();
|
||||
btn.disabled = false;
|
||||
btn.addEventListener("click", (e) => solveCaptchaRunner(worker, e));
|
||||
});
|
||||
};
|
||||
|
||||
export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
||||
export const solveCaptchaRunner = async (worker: Worker, e: Event): Promise<void> => {
|
||||
const PROGRESS_FILL = <HTMLElement>document.querySelector(".progress__fill");
|
||||
|
||||
const setWidth = (width: number) => {
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
PROGRESS_FILL.ariaValueNow = <any>parseInt(<any>width);
|
||||
};
|
||||
|
||||
let width = 0;
|
||||
|
||||
if (LOCK) {
|
||||
@@ -36,8 +56,9 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
||||
LOCK = true;
|
||||
if (CONST.btn().checked == false) {
|
||||
width = 0;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
setWidth(width);
|
||||
CONST.messageText().before();
|
||||
CONST.btn().ariaChecked = <any>false;
|
||||
LOCK = false;
|
||||
return;
|
||||
}
|
||||
@@ -57,7 +78,7 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
||||
|
||||
if (resp.type === "work") {
|
||||
width = 80;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
setWidth(width);
|
||||
console.log(
|
||||
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.value.work.time}`
|
||||
);
|
||||
@@ -72,22 +93,23 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
||||
};
|
||||
|
||||
width = 90;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
setWidth(width);
|
||||
// 3. submit work
|
||||
const token = await sendWork(proof);
|
||||
// 4. send token
|
||||
sendToParent(token);
|
||||
// 5. mark checkbox checked
|
||||
CONST.btn().checked = true;
|
||||
CONST.btn().ariaChecked = <any>true;
|
||||
width = 100;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
setWidth(width);
|
||||
CONST.messageText().after();
|
||||
LOCK = false;
|
||||
}
|
||||
if (resp.type === "progress") {
|
||||
if (width < 80) {
|
||||
width = (resp.nonce / max_recorded_nonce) * 100;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
width = resp.nonce / max_recorded_nonce * 100;
|
||||
setWidth(width);
|
||||
}
|
||||
console.log(`received nonce ${resp.nonce}`);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ body {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #f6f6f6;
|
||||
border: 2px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.widget__noscript-container {
|
||||
@@ -56,7 +58,8 @@ body {
|
||||
|
||||
.widget__verification-container {
|
||||
align-items: center;
|
||||
display: none;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
line-height: 30px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -67,36 +70,6 @@ body {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#widget__verification-text--during {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#widget__verification-text--after {
|
||||
display: none;
|
||||
color: green;
|
||||
}
|
||||
|
||||
#widget__verification-text--error {
|
||||
display: none;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--during {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -127,6 +100,29 @@ body {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.widget__container {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
.widget__inner-container {
|
||||
background-color: #1c1c1c;
|
||||
border: 2px solid #656569;
|
||||
}
|
||||
|
||||
.widget__verification-container {
|
||||
color: rgb(232, 230, 227);
|
||||
}
|
||||
|
||||
.widget__mcaptcha-brand-name {
|
||||
color: #7d94f9;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-info-link {
|
||||
color: #7d94f9;
|
||||
}
|
||||
}
|
||||
|
||||
/* progress bar courtesy of https://codepen.io/Bizzy-Coding/pen/poOymVJ?editors=1111 */
|
||||
.progress__bar {
|
||||
position: relative;
|
||||
@@ -142,3 +138,9 @@ body {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.progress__bar {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ const prove = async (
|
||||
config.string,
|
||||
config.difficulty_factor,
|
||||
STEPS,
|
||||
progress
|
||||
(nonce: bigint | number) => progress(Number(nonce))
|
||||
);
|
||||
const t1 = performance.now();
|
||||
time = t1 - t0;
|
||||
|
||||
@@ -9,6 +9,12 @@ import prove from "./prove";
|
||||
import { PoWConfig, ServiceWorkerMessage, ServiceWorkerWork } from "./types";
|
||||
|
||||
log.log("worker registered");
|
||||
|
||||
const ready: ServiceWorkerMessage = {
|
||||
type: "ready",
|
||||
};
|
||||
postMessage(ready);
|
||||
|
||||
onmessage = async (e) => {
|
||||
console.debug("message received at worker");
|
||||
const config: PoWConfig = e.data;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as CONST from "../const";
|
||||
|
||||
import {getBaseHtml, sitekey, checkbox} from "./setupTests";
|
||||
import { getBaseHtml, sitekey, checkbox } from "./setupTests";
|
||||
import * as TESTElements from "./setupTests";
|
||||
|
||||
it("const works", () => {
|
||||
@@ -17,29 +17,17 @@ it("const works", () => {
|
||||
|
||||
// display after
|
||||
CONST.messageText().after();
|
||||
expect(TESTElements.afterMsg.style.display).toBe("block");
|
||||
expect(TESTElements.beforeMsg.style.display).toBe("none");
|
||||
expect(TESTElements.duringMsg.style.display).toBe("none");
|
||||
expect(TESTElements.errorMsg.style.display).toBe("none");
|
||||
expect(TESTElements.Msg.innerText).toBe(CONST.AFTER);
|
||||
|
||||
// display before
|
||||
CONST.messageText().before();
|
||||
expect(TESTElements.afterMsg.style.display).toBe("none");
|
||||
expect(TESTElements.beforeMsg.style.display).toBe("block");
|
||||
expect(TESTElements.duringMsg.style.display).toBe("none");
|
||||
expect(TESTElements.errorMsg.style.display).toBe("none");
|
||||
expect(TESTElements.Msg.innerText).toBe(CONST.BEFORE);
|
||||
|
||||
// display during
|
||||
CONST.messageText().during();
|
||||
expect(TESTElements.afterMsg.style.display).toBe("none");
|
||||
expect(TESTElements.beforeMsg.style.display).toBe("none");
|
||||
expect(TESTElements.duringMsg.style.display).toBe("block");
|
||||
expect(TESTElements.errorMsg.style.display).toBe("none");
|
||||
expect(TESTElements.Msg.innerText).toBe(CONST.DURING);
|
||||
|
||||
// display error
|
||||
CONST.messageText().error();
|
||||
expect(TESTElements.afterMsg.style.display).toBe("none");
|
||||
expect(TESTElements.beforeMsg.style.display).toBe("none");
|
||||
expect(TESTElements.duringMsg.style.display).toBe("none");
|
||||
expect(TESTElements.errorMsg.style.display).toBe("block");
|
||||
expect(TESTElements.Msg.innerText).toBe(CONST.ERROR);
|
||||
});
|
||||
|
||||
@@ -11,25 +11,19 @@ export const checkbox = <HTMLInputElement>document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.id = CONST.btnId;
|
||||
|
||||
const getMessages = (state: string) => {
|
||||
const getMessages = () => {
|
||||
const msg = <HTMLElement>document.createElement("span");
|
||||
msg.id = `widget__verification-text--${state}`;
|
||||
msg.id = "widget__verification-text";
|
||||
msg.innerText = "I'm not a robot";
|
||||
return msg;
|
||||
};
|
||||
|
||||
export const beforeMsg = getMessages("before");
|
||||
export const afterMsg = getMessages("after");
|
||||
export const duringMsg = getMessages("during");
|
||||
export const errorMsg = getMessages("error");
|
||||
export const Msg = getMessages();
|
||||
|
||||
/** get base HTML with empty mCaptcha container */
|
||||
export const getBaseHtml = (): HTMLFormElement => {
|
||||
const form = <HTMLFormElement>document.createElement("form");
|
||||
form.appendChild(checkbox);
|
||||
form.appendChild(beforeMsg);
|
||||
form.appendChild(duringMsg);
|
||||
form.appendChild(afterMsg);
|
||||
form.appendChild(errorMsg);
|
||||
|
||||
form.appendChild(Msg);
|
||||
return form;
|
||||
};
|
||||
|
||||
@@ -40,5 +40,6 @@ export type Token = {
|
||||
};
|
||||
|
||||
export type ServiceWorkerMessage =
|
||||
| { type: "ready" }
|
||||
| { type: "work"; value: ServiceWorkerWork }
|
||||
| { type: "progress"; nonce: number };
|
||||
|
||||
46
utils/cache-bust/Cargo.lock
generated
46
utils/cache-bust/Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
@@ -177,6 +177,12 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -195,18 +201,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -228,33 +234,45 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.189"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.189"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"syn 2.0.93",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -287,9 +305,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
version = "2.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
Reference in New Issue
Block a user