Compare commits
42 commits
update-dep
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8bed3cb352 | ||
![]() |
8e03290fda | ||
![]() |
321fd2e89b | ||
![]() |
36600e2f13 | ||
![]() |
606d22cc9d | ||
![]() |
4426057fbc | ||
![]() |
1f23999c10 | ||
![]() |
0a3d93453e | ||
![]() |
939fb5f8b9 | ||
![]() |
3a787a6592 | ||
![]() |
9dfb0713ad | ||
![]() |
ad4582cc16 | ||
![]() |
77e4a9c473 | ||
![]() |
b6497882d7 | ||
![]() |
49a8757ead | ||
![]() |
1107d3fc05 | ||
![]() |
072a997ff0 | ||
![]() |
d30b53f4ee | ||
![]() |
b3021c500e | ||
![]() |
79006f1e64 | ||
![]() |
a45840d259 | ||
![]() |
960283324d | ||
![]() |
74364c4e17 | ||
![]() |
3d02f55241 | ||
![]() |
eab146b121 | ||
![]() |
d4534c1c43 | ||
![]() |
d5617c7ec7 | ||
![]() |
f933a30e7e | ||
![]() |
87785b38be | ||
![]() |
52c2c6e598 | ||
![]() |
b6a6705449 | ||
![]() |
c56b04fa5a | ||
![]() |
ccb9278d67 | ||
![]() |
eb69e9aedc | ||
![]() |
1310c22bed | ||
![]() |
b300d2caac | ||
![]() |
5d03682c45 | ||
![]() |
61729c5fae | ||
![]() |
8ec5122f87 | ||
![]() |
6bd66e6d00 | ||
![]() |
4739c697b7 | ||
![]() |
ce73d29792 |
80 changed files with 5063 additions and 7473 deletions
|
@ -1,2 +1,2 @@
|
|||
export POSTGRES_DATABASE_URL="postgres://postgres:password@localhost:5432/postgres"
|
||||
export MARIA_DATABASE_URL="mysql://maria:password@localhost:3306/maria"
|
||||
export MARIA_DATABASE_URL="mysql://root:password@localhost:3306/maria"
|
||||
|
|
2
.github/workflows/clippy-fmt.yml
vendored
2
.github/workflows/clippy-fmt.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
|||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "18.0.0"
|
||||
node-version: "20.0.0"
|
||||
|
||||
- name: Build frontend
|
||||
run: make frontend
|
||||
|
|
238
.github/workflows/coverage.yml
vendored
238
.github/workflows/coverage.yml
vendored
|
@ -1,119 +1,119 @@
|
|||
name: Coverage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- db-abstract
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- stable
|
||||
#- 1.51.0
|
||||
|
||||
name: ${{ matrix.version }} - 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: 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: "18.0.0"
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Build frontend
|
||||
run: make frontend
|
||||
|
||||
- name: Run the frontend tests
|
||||
run: make test.frontend
|
||||
|
||||
- name: Run migrations
|
||||
run: make migrate
|
||||
env:
|
||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
|
||||
- name: build frontend
|
||||
run: make frontend
|
||||
|
||||
- name: Generate coverage file
|
||||
if: github.event_name == 'pull_request'
|
||||
#if: (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
uses: actions-rs/tarpaulin@v0.1
|
||||
with:
|
||||
args: "-t 1200"
|
||||
env:
|
||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
# GIT_HASH is dummy value. I guess build.rs is skipped in tarpaulin
|
||||
# execution so this value is required for preventing meta tests from
|
||||
# panicking
|
||||
GIT_HASH: 8e77345f1597e40c2e266cb4e6dee74888918a61
|
||||
CACHE_BUSTER_FILE_MAP: '{"map":{"./static/bundle/main.js":"./prod/bundle/main.1417115E59909BE0A01040A45A398ADB09D928DF89CCF038FA44B14850442096.js"},"base_dir":"./prod"}'
|
||||
COMPILED_DATE: "2021-07-21"
|
||||
|
||||
- name: Upload to Codecov
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: codecov/codecov-action@v2
|
||||
#name: Coverage
|
||||
#
|
||||
#on:
|
||||
# pull_request:
|
||||
# types: [opened, synchronize, reopened]
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
# - db-abstract
|
||||
#
|
||||
#jobs:
|
||||
# build_and_test:
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# version:
|
||||
# - stable
|
||||
# #- 1.51.0
|
||||
#
|
||||
# name: ${{ matrix.version }} - 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: 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: "18.0.0"
|
||||
#
|
||||
# - uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
#
|
||||
# - name: Build frontend
|
||||
# run: make frontend
|
||||
#
|
||||
# - name: Run the frontend tests
|
||||
# run: make test.frontend
|
||||
#
|
||||
# - name: Run migrations
|
||||
# run: make migrate
|
||||
# env:
|
||||
# POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
# MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
#
|
||||
# - name: build frontend
|
||||
# run: make frontend
|
||||
#
|
||||
# - name: Generate coverage file
|
||||
# if: github.event_name == 'pull_request'
|
||||
# #if: (github.ref == 'refs/heads/master' || github.event_name == 'pull_request')
|
||||
# uses: actions-rs/tarpaulin@v0.1
|
||||
# with:
|
||||
# args: "-t 1200"
|
||||
# env:
|
||||
# POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
# MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
# # GIT_HASH is dummy value. I guess build.rs is skipped in tarpaulin
|
||||
# # execution so this value is required for preventing meta tests from
|
||||
# # panicking
|
||||
# GIT_HASH: 8e77345f1597e40c2e266cb4e6dee74888918a61
|
||||
# CACHE_BUSTER_FILE_MAP: '{"map":{"./static/bundle/main.js":"./prod/bundle/main.1417115E59909BE0A01040A45A398ADB09D928DF89CCF038FA44B14850442096.js"},"base_dir":"./prod"}'
|
||||
# COMPILED_DATE: "2021-07-21"
|
||||
#
|
||||
# - name: Upload to Codecov
|
||||
# if: github.event_name == 'pull_request'
|
||||
# uses: codecov/codecov-action@v2
|
||||
|
|
16
.github/workflows/linux.yml
vendored
16
.github/workflows/linux.yml
vendored
|
@ -84,7 +84,7 @@ jobs:
|
|||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "18.0.0"
|
||||
node-version: "20.0.0"
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
|
@ -119,7 +119,7 @@ jobs:
|
|||
run: make test.integration
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
|
||||
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && github.repository == 'mCaptcha/mCaptcha'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: mcaptcha
|
||||
|
@ -129,12 +129,12 @@ jobs:
|
|||
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
|
||||
run: make docker-publish
|
||||
|
||||
# - name: publish bins
|
||||
# if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
|
||||
# run: ./scripts/publish.sh publish master latest $DUMBSERVE_PASSWORD
|
||||
# env:
|
||||
# DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
|
||||
# GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
|
||||
- name: publish bins
|
||||
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
|
||||
run: ./scripts/publish.sh publish master latest $DUMBSERVE_PASSWORD
|
||||
env:
|
||||
DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
|
||||
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
|
||||
|
||||
- name: generate documentation
|
||||
if: matrix.version == 'stable' && (github.repository == 'mCaptcha/mCaptcha')
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
18
|
||||
20
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
### Changed
|
||||
|
||||
- 2023-10-18: Environment variable names have changed, please see
|
||||
[CONFIGURATION.md](docs/CONFIGURATION.md) for the names of environment
|
||||
variables.
|
||||
- ([`7d0e4c6`](https://github.com/mCaptcha/mCaptcha/commit/7d0e4c6be4b0769921cda7681858ebe16ec9a07b)) Add `secret` parameter to token verification request payload(`/api/v1/pow/siteverify`) to mitigate a security issue that @gusted found:
|
||||
> ...A malicious user could grab the sitekey
|
||||
> and use that sitekey with mcaptcha to use it for their own server.
|
||||
|
|
172
Cargo.lock
generated
172
Cargo.lock
generated
|
@ -439,6 +439,19 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.53"
|
||||
|
@ -1532,6 +1545,17 @@ dependencies = [
|
|||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
|
@ -1559,6 +1583,43 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
|
@ -1651,6 +1712,12 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
|
@ -1938,6 +2005,7 @@ dependencies = [
|
|||
"openssl",
|
||||
"pretty_env_logger 0.4.0",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"rust-embed",
|
||||
"sailfish",
|
||||
"serde",
|
||||
|
@ -2639,6 +2707,46 @@ version = "0.7.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64 0.21.2",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
|
@ -3369,6 +3477,27 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.6.0"
|
||||
|
@ -3572,6 +3701,12 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
|
@ -3605,6 +3740,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
|
@ -3779,6 +3920,15 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -3810,6 +3960,18 @@ dependencies = [
|
|||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.84"
|
||||
|
@ -4027,6 +4189,16 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
|
|
@ -78,6 +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"] }
|
||||
|
||||
|
||||
[dependencies.db-core]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
FROM node:18.0.0 as frontend
|
||||
FROM node:20 as frontend
|
||||
RUN set -ex; \
|
||||
apt-get update; \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
|
|
|
@ -66,3 +66,8 @@ url = "127.0.0.1"
|
|||
port = 10025
|
||||
username = "admin"
|
||||
password = "password"
|
||||
|
||||
#[survey]
|
||||
#nodes = ["http://localhost:7001"]
|
||||
#rate_limit = 10 # upload every hour
|
||||
#instance_root_url = "http://localhost:7000"
|
||||
|
|
|
@ -289,6 +289,35 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
|||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all psuedo IDs
|
||||
async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult<Vec<String>>;
|
||||
|
||||
/// Track maximum nonce received against captcha levels
|
||||
async fn update_max_nonce_for_level(
|
||||
&self,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
latest_nonce: u32,
|
||||
) -> DBResult<()>;
|
||||
|
||||
/// Get maximum nonce tracked so far for captcha levels
|
||||
async fn get_max_nonce_for_level(
|
||||
&self,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
) -> DBResult<u32>;
|
||||
|
||||
/// Get number of analytics entries that are under a certain duration
|
||||
async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize>;
|
||||
|
||||
/// Get the entry at a location in the list of analytics entires under a certain time limit
|
||||
/// and sorted in ascending order
|
||||
async fn stats_get_entry_at_location_for_time_limit_asc(
|
||||
&self,
|
||||
duration: u32,
|
||||
location: u32,
|
||||
) -> DBResult<Option<usize>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||
|
|
|
@ -7,6 +7,29 @@
|
|||
use crate::errors::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// easy traffic pattern
|
||||
pub const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern {
|
||||
avg_traffic: 500,
|
||||
peak_sustainable_traffic: 5_000,
|
||||
broke_my_site_traffic: Some(10_000),
|
||||
};
|
||||
|
||||
/// levels for complex captcha config
|
||||
pub const LEVELS: [Level; 3] = [
|
||||
Level {
|
||||
difficulty_factor: 1,
|
||||
visitor_threshold: 1,
|
||||
},
|
||||
Level {
|
||||
difficulty_factor: 2,
|
||||
visitor_threshold: 2,
|
||||
},
|
||||
Level {
|
||||
difficulty_factor: 3,
|
||||
visitor_threshold: 3,
|
||||
},
|
||||
];
|
||||
|
||||
/// test all database functions
|
||||
pub async fn database_works<'a, T: MCDatabase>(
|
||||
db: &T,
|
||||
|
@ -250,7 +273,6 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||
db.record_confirm(c.key).await.unwrap();
|
||||
|
||||
// analytics start
|
||||
|
||||
db.analytics_create_psuedo_id_if_not_exists(c.key)
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -258,6 +280,12 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||
.analytics_get_psuedo_id_from_capmaign_id(c.key)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vec![psuedo_id.clone()],
|
||||
db.analytics_get_all_psuedo_ids(0).await.unwrap()
|
||||
);
|
||||
assert!(db.analytics_get_all_psuedo_ids(1).await.unwrap().is_empty());
|
||||
|
||||
db.analytics_create_psuedo_id_if_not_exists(c.key)
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -267,6 +295,7 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||
.await
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
c.key,
|
||||
db.analytics_get_capmaign_id_from_psuedo_id(&psuedo_id)
|
||||
|
@ -275,11 +304,31 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||
);
|
||||
|
||||
let analytics = CreatePerformanceAnalytics {
|
||||
time: 0,
|
||||
difficulty_factor: 0,
|
||||
time: 1,
|
||||
difficulty_factor: 1,
|
||||
worker_type: "wasm".into(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
db.stats_get_num_logs_under_time(analytics.time)
|
||||
.await
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
|
||||
db.analysis_save(c.key, &analytics).await.unwrap();
|
||||
assert_eq!(
|
||||
db.stats_get_num_logs_under_time(analytics.time)
|
||||
.await
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
db.stats_get_num_logs_under_time(analytics.time - 1)
|
||||
.await
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
let limit = 50;
|
||||
let mut offset = 0;
|
||||
let a = db.analytics_fetch(c.key, limit, offset).await.unwrap();
|
||||
|
@ -298,11 +347,82 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||
.unwrap();
|
||||
assert_eq!(db.analytics_fetch(c.key, 1000, 0).await.unwrap().len(), 0);
|
||||
assert!(!db.analytics_captcha_is_published(c.key).await.unwrap());
|
||||
|
||||
let rest_analytics = [
|
||||
CreatePerformanceAnalytics {
|
||||
time: 2,
|
||||
difficulty_factor: 2,
|
||||
worker_type: "wasm".into(),
|
||||
},
|
||||
CreatePerformanceAnalytics {
|
||||
time: 3,
|
||||
difficulty_factor: 3,
|
||||
worker_type: "wasm".into(),
|
||||
},
|
||||
CreatePerformanceAnalytics {
|
||||
time: 4,
|
||||
difficulty_factor: 4,
|
||||
worker_type: "wasm".into(),
|
||||
},
|
||||
CreatePerformanceAnalytics {
|
||||
time: 5,
|
||||
difficulty_factor: 5,
|
||||
worker_type: "wasm".into(),
|
||||
},
|
||||
];
|
||||
for a in rest_analytics.iter() {
|
||||
db.analysis_save(c.key, &a).await.unwrap();
|
||||
}
|
||||
assert!(db
|
||||
.stats_get_entry_at_location_for_time_limit_asc(1, 2)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
db.stats_get_entry_at_location_for_time_limit_asc(2, 1)
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
db.stats_get_entry_at_location_for_time_limit_asc(3, 2)
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(3)
|
||||
);
|
||||
|
||||
db.analytics_delete_all_records_for_campaign(c.key)
|
||||
.await
|
||||
.unwrap();
|
||||
// analytics end
|
||||
|
||||
// nonce tracking start
|
||||
assert_eq!(
|
||||
db.get_max_nonce_for_level(c.key, l[0].difficulty_factor)
|
||||
.await
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
db.update_max_nonce_for_level(c.key, l[0].difficulty_factor, 1000)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
db.get_max_nonce_for_level(c.key, l[0].difficulty_factor)
|
||||
.await
|
||||
.unwrap(),
|
||||
1000
|
||||
);
|
||||
db.update_max_nonce_for_level(c.key, l[0].difficulty_factor, 10_000)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
db.get_max_nonce_for_level(c.key, l[0].difficulty_factor)
|
||||
.await
|
||||
.unwrap(),
|
||||
10_000
|
||||
);
|
||||
// nonce tracking end
|
||||
|
||||
assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1);
|
||||
assert_eq!(
|
||||
db.fetch_config_fetched(p.username, c.key)
|
||||
|
|
12
db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json
generated
Normal file
12
db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n AND\n visitor_threshold = ?\n ), ?);",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc"
|
||||
}
|
12
db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json
generated
Normal file
12
db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "UPDATE mcaptcha_track_nonce SET nonce = ?\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n )\n AND nonce <= ?;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea"
|
||||
}
|
25
db/db-sqlx-maria/.sqlx/query-9bae79667a8cc631541879321e72a40f20cf812584aaf44418089bc7a51e07c4.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-9bae79667a8cc631541879321e72a40f20cf812584aaf44418089bc7a51e07c4.json
generated
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "SELECT\n COUNT(difficulty_factor) AS count\n FROM\n mcaptcha_pow_analytics\n WHERE time <= ?;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9bae79667a8cc631541879321e72a40f20cf812584aaf44418089bc7a51e07c4"
|
||||
}
|
12
db/db-sqlx-maria/.sqlx/query-9def82dcec9c8d477824182bb2f71044cc264cf2073ab4f60a0000b435ed0f0b.json
generated
Normal file
12
db/db-sqlx-maria/.sqlx/query-9def82dcec9c8d477824182bb2f71044cc264cf2073ab4f60a0000b435ed0f0b.json
generated
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key =?)\n AND\n difficulty_factor = ?\n ), ?);",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9def82dcec9c8d477824182bb2f71044cc264cf2073ab4f60a0000b435ed0f0b"
|
||||
}
|
25
db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json
generated
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "SELECT nonce FROM mcaptcha_track_nonce\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n );",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "nonce",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 11
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb"
|
||||
}
|
25
db/db-sqlx-maria/.sqlx/query-c4d6ad934e38218931e74ae1c31c6712cbadb40f31bb12e160c9d333c7e3835c.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-c4d6ad934e38218931e74ae1c31c6712cbadb40f31bb12e160c9d333c7e3835c.json
generated
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "SELECT\n difficulty_factor\n FROM\n mcaptcha_pow_analytics\n WHERE\n time <= ?\n ORDER BY difficulty_factor ASC LIMIT 1 OFFSET ?;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "difficulty_factor",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 11
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c4d6ad934e38218931e74ae1c31c6712cbadb40f31bb12e160c9d333c7e3835c"
|
||||
}
|
25
db/db-sqlx-maria/.sqlx/query-e2c30dafa790b388a193ad8785c0a7d88d8e7a7558775e238fe009f478003e46.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-e2c30dafa790b388a193ad8785c0a7d88d8e7a7558775e238fe009f478003e46.json
generated
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n SELECT\n psuedo_id\n FROM\n mcaptcha_psuedo_campaign_id\n ORDER BY ID ASC LIMIT ? OFFSET ?;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "psuedo_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 400
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e2c30dafa790b388a193ad8785c0a7d88d8e7a7558775e238fe009f478003e46"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
-- Add migration script here
|
||||
CREATE TABLE IF NOT EXISTS mcaptcha_track_nonce (
|
||||
level_id INTEGER NOT NULL,
|
||||
nonce INTEGER NOT NULL DEFAULT 0,
|
||||
ID INT auto_increment,
|
||||
PRIMARY KEY(ID),
|
||||
CONSTRAINT `fk_mcaptcha_track_nonce_level_id`
|
||||
FOREIGN KEY (level_id)
|
||||
REFERENCES mcaptcha_levels (level_id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
|
@ -433,6 +433,39 @@ impl MCDatabase for Database {
|
|||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
let mut futs = Vec::with_capacity(levels.len());
|
||||
|
||||
for level in levels.iter() {
|
||||
let difficulty_factor = level.difficulty_factor as i32;
|
||||
let visitor_threshold = level.visitor_threshold as i32;
|
||||
let fut = sqlx::query!(
|
||||
"INSERT INTO
|
||||
mcaptcha_track_nonce (level_id, nonce)
|
||||
VALUES ((
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
|
||||
AND
|
||||
difficulty_factor = ?
|
||||
AND
|
||||
visitor_threshold = ?
|
||||
), ?);",
|
||||
&captcha_key,
|
||||
difficulty_factor,
|
||||
visitor_threshold,
|
||||
0,
|
||||
)
|
||||
.execute(&self.pool);
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
@ -987,12 +1020,8 @@ impl MCDatabase for Database {
|
|||
&self,
|
||||
captcha_id: &str,
|
||||
) -> DBResult<String> {
|
||||
struct ID {
|
||||
psuedo_id: String,
|
||||
}
|
||||
|
||||
let res = sqlx::query_as!(
|
||||
ID,
|
||||
PsuedoID,
|
||||
"SELECT psuedo_id FROM
|
||||
mcaptcha_psuedo_campaign_id
|
||||
WHERE
|
||||
|
@ -1069,6 +1098,182 @@ impl MCDatabase for Database {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
/// Get all psuedo IDs
|
||||
async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult<Vec<String>> {
|
||||
const LIMIT: usize = 50;
|
||||
let offset = LIMIT * page;
|
||||
|
||||
let mut res = sqlx::query_as!(
|
||||
PsuedoID,
|
||||
"
|
||||
SELECT
|
||||
psuedo_id
|
||||
FROM
|
||||
mcaptcha_psuedo_campaign_id
|
||||
ORDER BY ID ASC LIMIT ? OFFSET ?;",
|
||||
LIMIT as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
|
||||
}
|
||||
|
||||
/// Track maximum nonce received against captcha levels
|
||||
async fn update_max_nonce_for_level(
|
||||
&self,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
latest_nonce: u32,
|
||||
) -> DBResult<()> {
|
||||
let latest_nonce = latest_nonce as i64;
|
||||
sqlx::query!(
|
||||
"UPDATE mcaptcha_track_nonce SET nonce = ?
|
||||
WHERE level_id = (
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
|
||||
AND
|
||||
difficulty_factor = ?
|
||||
)
|
||||
AND nonce <= ?;",
|
||||
latest_nonce,
|
||||
&captcha_key,
|
||||
difficulty_factor as i64,
|
||||
latest_nonce
|
||||
)
|
||||
.execute(&self.pool).await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get maximum nonce tracked so far for captcha levels
|
||||
async fn get_max_nonce_for_level(
|
||||
&self,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
) -> DBResult<u32> {
|
||||
struct X {
|
||||
nonce: i32,
|
||||
}
|
||||
|
||||
async fn inner_get_max_nonce(
|
||||
pool: &MySqlPool,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
) -> DBResult<X> {
|
||||
sqlx::query_as!(
|
||||
X,
|
||||
"SELECT nonce FROM mcaptcha_track_nonce
|
||||
WHERE level_id = (
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
|
||||
AND
|
||||
difficulty_factor = ?
|
||||
);",
|
||||
&captcha_key,
|
||||
difficulty_factor as i32,
|
||||
)
|
||||
.fetch_one(pool).await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))
|
||||
}
|
||||
|
||||
let res = inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await;
|
||||
if let Err(DBError::CaptchaNotFound) = res {
|
||||
sqlx::query!(
|
||||
"INSERT INTO
|
||||
mcaptcha_track_nonce (level_id, nonce)
|
||||
VALUES ((
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key =?)
|
||||
AND
|
||||
difficulty_factor = ?
|
||||
), ?);",
|
||||
&captcha_key,
|
||||
difficulty_factor as i32,
|
||||
0,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
let res =
|
||||
inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await?;
|
||||
Ok(res.nonce as u32)
|
||||
} else {
|
||||
let res = res?;
|
||||
Ok(res.nonce as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get number of analytics entries that are under a certain duration
|
||||
async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize> {
|
||||
struct Count {
|
||||
count: Option<i64>,
|
||||
}
|
||||
|
||||
//"SELECT COUNT(*) FROM (SELECT difficulty_factor FROM mcaptcha_pow_analytics WHERE time <= ?) as count",
|
||||
let count = sqlx::query_as!(
|
||||
Count,
|
||||
"SELECT
|
||||
COUNT(difficulty_factor) AS count
|
||||
FROM
|
||||
mcaptcha_pow_analytics
|
||||
WHERE time <= ?;",
|
||||
duration as i32,
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
Ok(count.count.unwrap_or_else(|| 0) as usize)
|
||||
}
|
||||
|
||||
/// Get the entry at a location in the list of analytics entires under a certain time limited
|
||||
/// and sorted in ascending order
|
||||
async fn stats_get_entry_at_location_for_time_limit_asc(
|
||||
&self,
|
||||
duration: u32,
|
||||
location: u32,
|
||||
) -> DBResult<Option<usize>> {
|
||||
struct Difficulty {
|
||||
difficulty_factor: Option<i32>,
|
||||
}
|
||||
|
||||
match sqlx::query_as!(
|
||||
Difficulty,
|
||||
"SELECT
|
||||
difficulty_factor
|
||||
FROM
|
||||
mcaptcha_pow_analytics
|
||||
WHERE
|
||||
time <= ?
|
||||
ORDER BY difficulty_factor ASC LIMIT 1 OFFSET ?;",
|
||||
duration as i32,
|
||||
location as i64 - 1,
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(Some(res.difficulty_factor.unwrap() as usize)),
|
||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -1134,3 +1339,7 @@ impl From<InternaleCaptchaConfig> for Captcha {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PsuedoID {
|
||||
psuedo_id: String,
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
#![cfg(test)]
|
||||
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use std::env;
|
||||
|
||||
use sqlx::{migrate::MigrateDatabase, mysql::MySqlPoolOptions};
|
||||
use url::Url;
|
||||
|
||||
use crate::*;
|
||||
|
||||
use db_core::tests::*;
|
||||
|
@ -26,28 +28,6 @@ async fn everyting_works() {
|
|||
const HEADING: &str = "testing notifications get db mariadb";
|
||||
const MESSAGE: &str = "testing notifications get message db mariadb";
|
||||
|
||||
// easy traffic pattern
|
||||
const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern {
|
||||
avg_traffic: 500,
|
||||
peak_sustainable_traffic: 5_000,
|
||||
broke_my_site_traffic: Some(10_000),
|
||||
};
|
||||
|
||||
const LEVELS: [Level; 3] = [
|
||||
Level {
|
||||
difficulty_factor: 1,
|
||||
visitor_threshold: 1,
|
||||
},
|
||||
Level {
|
||||
difficulty_factor: 2,
|
||||
visitor_threshold: 2,
|
||||
},
|
||||
Level {
|
||||
difficulty_factor: 3,
|
||||
visitor_threshold: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const ADD_NOTIFICATION: AddNotification = AddNotification {
|
||||
from: NAME,
|
||||
to: NAME,
|
||||
|
@ -56,10 +36,20 @@ async fn everyting_works() {
|
|||
};
|
||||
|
||||
let url = env::var("MARIA_DATABASE_URL").unwrap();
|
||||
|
||||
let mut parsed = Url::parse(&url).unwrap();
|
||||
parsed.set_path("db_maria_test");
|
||||
let url = parsed.to_string();
|
||||
|
||||
if sqlx::MySql::database_exists(&url).await.unwrap() {
|
||||
sqlx::MySql::drop_database(&url).await.unwrap();
|
||||
}
|
||||
sqlx::MySql::create_database(&url).await.unwrap();
|
||||
|
||||
let pool_options = MySqlPoolOptions::new().max_connections(2);
|
||||
let connection_options = ConnectionOptions::Fresh(Fresh {
|
||||
pool_options,
|
||||
url,
|
||||
url: url.clone(),
|
||||
disable_logging: false,
|
||||
});
|
||||
let db = connection_options.connect().await.unwrap();
|
||||
|
@ -78,4 +68,6 @@ async fn everyting_works() {
|
|||
description: CAPTCHA_DESCRIPTION,
|
||||
};
|
||||
database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await;
|
||||
drop(db);
|
||||
sqlx::MySql::drop_database(&url).await.unwrap();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n AND\n visitor_threshold = $3\n ), $4);",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "133ee23ab5ac7c664a86b6edfaa8da79281b6d1f5ba33c642a6ea1b0682fe0b0"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT nonce FROM mcaptcha_track_nonce\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n );",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "nonce",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "96f1f1e45144d5add6c4ba4cd2df8eda6043bc8cd6952787f92a687fef778a6e"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(difficulty_factor) FROM mcaptcha_pow_analytics WHERE time <= $1;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c08c1dd4bfcb6cbd0359c79cc3be79526a012b006ce9deb80bceb4e1a04c835d"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT\n difficulty_factor\n FROM\n mcaptcha_pow_analytics\n WHERE\n time <= $1\n ORDER BY difficulty_factor ASC LIMIT 1 OFFSET $2;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "difficulty_factor",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c67aec0c3d5786fb495b6ed60fa106437d8e5034d3a40bf8face2ca7c12f2694"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n psuedo_id\n FROM\n mcaptcha_psuedo_campaign_id\n ORDER BY ID ASC LIMIT $1 OFFSET $2;",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "psuedo_id",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d6b89b032e3a65bb5739dde8901a0d6363939bdd87739b4292dd1d88e03ce6f7"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n ), $3);",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e0088cf77c1c3a0184f35d1899a6168023fba021adf281cf1c8f9e8ccfe3a03e"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE mcaptcha_track_nonce SET nonce = $3\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n )\n AND nonce <= $3;",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e33ee14cf76cd09d9a157b8784a3fe25b89eaca105aa30e479d31b756cd5c88b"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
-- Add migration script here
|
||||
CREATE TABLE IF NOT EXISTS mcaptcha_track_nonce (
|
||||
nonce INTEGER NOT NULL DEFAULT 0,
|
||||
level_id INTEGER references mcaptcha_levels(level_id) ON DELETE CASCADE,
|
||||
ID SERIAL PRIMARY KEY NOT NULL
|
||||
);
|
|
@ -445,6 +445,38 @@ impl MCDatabase for Database {
|
|||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
let mut futs = Vec::with_capacity(levels.len());
|
||||
for level in levels.iter() {
|
||||
let difficulty_factor = level.difficulty_factor as i32;
|
||||
let visitor_threshold = level.visitor_threshold as i32;
|
||||
let fut = sqlx::query!(
|
||||
"INSERT INTO
|
||||
mcaptcha_track_nonce (level_id, nonce)
|
||||
VALUES ((
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||
AND
|
||||
difficulty_factor = $2
|
||||
AND
|
||||
visitor_threshold = $3
|
||||
), $4);",
|
||||
&captcha_key,
|
||||
difficulty_factor,
|
||||
visitor_threshold,
|
||||
0,
|
||||
)
|
||||
.execute(&self.pool);
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
@ -994,12 +1026,8 @@ impl MCDatabase for Database {
|
|||
&self,
|
||||
captcha_id: &str,
|
||||
) -> DBResult<String> {
|
||||
struct ID {
|
||||
psuedo_id: String,
|
||||
}
|
||||
|
||||
let res = sqlx::query_as!(
|
||||
ID,
|
||||
PsuedoID,
|
||||
"SELECT psuedo_id FROM
|
||||
mcaptcha_psuedo_campaign_id
|
||||
WHERE
|
||||
|
@ -1078,6 +1106,177 @@ impl MCDatabase for Database {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all psuedo IDs
|
||||
async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult<Vec<String>> {
|
||||
const LIMIT: usize = 50;
|
||||
let offset = LIMIT * page;
|
||||
|
||||
let mut res = sqlx::query_as!(
|
||||
PsuedoID,
|
||||
"
|
||||
SELECT
|
||||
psuedo_id
|
||||
FROM
|
||||
mcaptcha_psuedo_campaign_id
|
||||
ORDER BY ID ASC LIMIT $1 OFFSET $2;",
|
||||
LIMIT as i64,
|
||||
offset as i64
|
||||
)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
|
||||
}
|
||||
|
||||
/// Track maximum nonce received against captcha levels
|
||||
async fn update_max_nonce_for_level(
|
||||
&self,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
latest_nonce: u32,
|
||||
) -> DBResult<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE mcaptcha_track_nonce SET nonce = $3
|
||||
WHERE level_id = (
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||
AND
|
||||
difficulty_factor = $2
|
||||
)
|
||||
AND nonce <= $3;",
|
||||
&captcha_key,
|
||||
difficulty_factor as i32,
|
||||
latest_nonce as i32,
|
||||
)
|
||||
.execute(&self.pool).await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get maximum nonce tracked so far for captcha levels
|
||||
async fn get_max_nonce_for_level(
|
||||
&self,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
) -> DBResult<u32> {
|
||||
struct X {
|
||||
nonce: i32,
|
||||
}
|
||||
|
||||
async fn inner_get_max_nonce(
|
||||
pool: &PgPool,
|
||||
captcha_key: &str,
|
||||
difficulty_factor: u32,
|
||||
) -> DBResult<X> {
|
||||
sqlx::query_as!(
|
||||
X,
|
||||
"SELECT nonce FROM mcaptcha_track_nonce
|
||||
WHERE level_id = (
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||
AND
|
||||
difficulty_factor = $2
|
||||
);",
|
||||
&captcha_key,
|
||||
difficulty_factor as i32,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))
|
||||
}
|
||||
|
||||
let res = inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await;
|
||||
if let Err(DBError::CaptchaNotFound) = res {
|
||||
sqlx::query!(
|
||||
"INSERT INTO
|
||||
mcaptcha_track_nonce (level_id, nonce)
|
||||
VALUES ((
|
||||
SELECT
|
||||
level_id
|
||||
FROM
|
||||
mcaptcha_levels
|
||||
WHERE
|
||||
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||
AND
|
||||
difficulty_factor = $2
|
||||
), $3);",
|
||||
&captcha_key,
|
||||
difficulty_factor as i32,
|
||||
0,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
let res =
|
||||
inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await?;
|
||||
Ok(res.nonce as u32)
|
||||
} else {
|
||||
let res = res?;
|
||||
Ok(res.nonce as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get number of analytics entries that are under a certain duration
|
||||
async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize> {
|
||||
struct Count {
|
||||
count: Option<i64>,
|
||||
}
|
||||
|
||||
let count = sqlx::query_as!(
|
||||
Count,
|
||||
"SELECT COUNT(difficulty_factor) FROM mcaptcha_pow_analytics WHERE time <= $1;",
|
||||
duration as i32,
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||
|
||||
Ok(count.count.unwrap_or_else(|| 0) as usize)
|
||||
}
|
||||
|
||||
/// Get the entry at a location in the list of analytics entires under a certain time limit
|
||||
/// and sorted in ascending order
|
||||
async fn stats_get_entry_at_location_for_time_limit_asc(
|
||||
&self,
|
||||
duration: u32,
|
||||
location: u32,
|
||||
) -> DBResult<Option<usize>> {
|
||||
struct Difficulty {
|
||||
difficulty_factor: Option<i32>,
|
||||
}
|
||||
|
||||
match sqlx::query_as!(
|
||||
Difficulty,
|
||||
"SELECT
|
||||
difficulty_factor
|
||||
FROM
|
||||
mcaptcha_pow_analytics
|
||||
WHERE
|
||||
time <= $1
|
||||
ORDER BY difficulty_factor ASC LIMIT 1 OFFSET $2;",
|
||||
duration as i32,
|
||||
location as i64 - 1,
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(Some(res.difficulty_factor.unwrap() as usize)),
|
||||
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -1125,6 +1324,10 @@ impl From<InnerNotification> for Notification {
|
|||
}
|
||||
}
|
||||
|
||||
struct PsuedoID {
|
||||
psuedo_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InternaleCaptchaConfig {
|
||||
config_id: i32,
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
|
||||
#![cfg(test)]
|
||||
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use std::env;
|
||||
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use url::Url;
|
||||
|
||||
use crate::*;
|
||||
|
||||
use db_core::tests::*;
|
||||
|
@ -26,28 +29,6 @@ async fn everyting_works() {
|
|||
const HEADING: &str = "testing notifications get db postgres";
|
||||
const MESSAGE: &str = "testing notifications get message db postgres";
|
||||
|
||||
// easy traffic pattern
|
||||
const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern {
|
||||
avg_traffic: 500,
|
||||
peak_sustainable_traffic: 5_000,
|
||||
broke_my_site_traffic: Some(10_000),
|
||||
};
|
||||
|
||||
const LEVELS: [Level; 3] = [
|
||||
Level {
|
||||
difficulty_factor: 1,
|
||||
visitor_threshold: 1,
|
||||
},
|
||||
Level {
|
||||
difficulty_factor: 2,
|
||||
visitor_threshold: 2,
|
||||
},
|
||||
Level {
|
||||
difficulty_factor: 3,
|
||||
visitor_threshold: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const ADD_NOTIFICATION: AddNotification = AddNotification {
|
||||
from: NAME,
|
||||
to: NAME,
|
||||
|
@ -56,10 +37,20 @@ async fn everyting_works() {
|
|||
};
|
||||
|
||||
let url = env::var("POSTGRES_DATABASE_URL").unwrap();
|
||||
|
||||
let mut parsed = Url::parse(&url).unwrap();
|
||||
parsed.set_path("db_postgres_test");
|
||||
let url = parsed.to_string();
|
||||
|
||||
if sqlx::Postgres::database_exists(&url).await.unwrap() {
|
||||
sqlx::Postgres::drop_database(&url).await.unwrap();
|
||||
}
|
||||
sqlx::Postgres::create_database(&url).await.unwrap();
|
||||
|
||||
let pool_options = PgPoolOptions::new().max_connections(2);
|
||||
let connection_options = ConnectionOptions::Fresh(Fresh {
|
||||
pool_options,
|
||||
url,
|
||||
url: url.clone(),
|
||||
disable_logging: false,
|
||||
});
|
||||
let db = connection_options.connect().await.unwrap();
|
||||
|
@ -78,4 +69,6 @@ async fn everyting_works() {
|
|||
description: CAPTCHA_DESCRIPTION,
|
||||
};
|
||||
database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await;
|
||||
drop(db);
|
||||
sqlx::Postgres::drop_database(&url).await.unwrap();
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ services:
|
|||
- 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
|
||||
MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
|
||||
RUST_LOG: "debug"
|
||||
PORT: 7000
|
||||
depends_on:
|
||||
- mcaptcha_postgres
|
||||
|
|
4642
docs/openapi/package-lock.json
generated
4642
docs/openapi/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,12 +11,12 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/mCaptcha/mCaptcha.git"
|
||||
},
|
||||
"license": "AGPL3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/mCaptcha/mCaptcha/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mCaptcha/mCaptcha#readme",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "^1.0.0-beta.129"
|
||||
"@redocly/cli": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
34
package.json
34
package.json
|
@ -10,35 +10,35 @@
|
|||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/sinon": "^10.0.15",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"@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",
|
||||
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"eslint": "^8.44.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"eslint": "^8.52.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jsdom": "^22.1.0",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"sass": "^1.63.6",
|
||||
"sass": "^1.69.5",
|
||||
"sass-loader": "^13.3.2",
|
||||
"sinon": "^15.2.0",
|
||||
"sinon": "^17.0.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.4.4",
|
||||
"ts-loader": "^9.5.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.88.1",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-alpha-1",
|
||||
"@mcaptcha/pow-wasm": "^0.1.0-alpha-1",
|
||||
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-3"
|
||||
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-rc2",
|
||||
"@mcaptcha/vanilla-glue": "^0.1.0-rc1",
|
||||
"@mcaptcha/pow-wasm": "^0.1.0-rc2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ DUMBSERVE_PASSWORD=$4
|
|||
DUMBSERVE_HOST="https://$DUMBSERVE_USERNAME:$DUMBSERVE_PASSWORD@dl.mcaptcha.org"
|
||||
|
||||
NAME=mcaptcha
|
||||
KEY=0CBABF3084E84E867A76709750BE39D10ECE01FB
|
||||
KEY=73DAC973A9ADBB9ADCB5CDC4595A08135BA9FF73
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
FILENAME="$NAME-$2-linux-amd64"
|
||||
|
|
|
@ -85,10 +85,18 @@ pub mod runner {
|
|||
data.db
|
||||
.add_captcha_levels(username, &key, &payload.levels)
|
||||
.await?;
|
||||
|
||||
if payload.publish_benchmarks {
|
||||
data.db
|
||||
.analytics_create_psuedo_id_if_not_exists(&key)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let mcaptcha_config = MCaptchaDetails {
|
||||
name: payload.description.clone(),
|
||||
key,
|
||||
};
|
||||
|
||||
Ok(mcaptcha_config)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ pub mod meta;
|
|||
pub mod notifications;
|
||||
pub mod pow;
|
||||
mod routes;
|
||||
pub mod stats;
|
||||
pub mod survey;
|
||||
|
||||
pub use routes::ROUTES;
|
||||
|
||||
|
@ -24,6 +26,8 @@ pub fn services(cfg: &mut ServiceConfig) {
|
|||
account::services(cfg);
|
||||
mcaptcha::services(cfg);
|
||||
notifications::services(cfg);
|
||||
survey::services(cfg);
|
||||
stats::services(cfg);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
//use actix::prelude::*;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use libmcaptcha::pow::PoWConfig;
|
||||
use libmcaptcha::{
|
||||
defense::LevelBuilder, master::messages::AddSiteBuilder, DefenseBuilder,
|
||||
MCaptchaBuilder,
|
||||
|
@ -21,7 +22,13 @@ pub struct GetConfigPayload {
|
|||
pub key: String,
|
||||
}
|
||||
|
||||
// API keys are mcaptcha actor names
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct ApiPoWConfig {
|
||||
pub string: String,
|
||||
pub difficulty_factor: u32,
|
||||
pub salt: String,
|
||||
pub max_recorded_nonce: u32,
|
||||
}
|
||||
|
||||
/// get PoW configuration for an mcaptcha key
|
||||
#[my_codegen::post(path = "V1_API_ROUTES.pow.get_config()")]
|
||||
|
@ -35,52 +42,34 @@ pub async fn get_config(
|
|||
}
|
||||
let payload = payload.into_inner();
|
||||
|
||||
match data.captcha.get_pow(payload.key.clone()).await {
|
||||
Ok(Some(config)) => {
|
||||
data.stats.record_fetch(&data, &payload.key).await?;
|
||||
Ok(HttpResponse::Ok().json(config))
|
||||
}
|
||||
Ok(None) => {
|
||||
init_mcaptcha(&data, &payload.key).await?;
|
||||
let config = data
|
||||
.captcha
|
||||
.get_pow(payload.key.clone())
|
||||
.await
|
||||
.expect("mcaptcha should be initialized and ready to go");
|
||||
// background it. would require data::Data to be static
|
||||
// to satidfy lifetime
|
||||
data.stats.record_fetch(&data, &payload.key).await?;
|
||||
Ok(HttpResponse::Ok().json(config))
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
let config: ServiceResult<PoWConfig> =
|
||||
match data.captcha.get_pow(payload.key.clone()).await {
|
||||
Ok(Some(config)) => Ok(config),
|
||||
Ok(None) => {
|
||||
init_mcaptcha(&data, &payload.key).await?;
|
||||
let config = data
|
||||
.captcha
|
||||
.get_pow(payload.key.clone())
|
||||
.await
|
||||
.expect("mcaptcha should be initialized and ready to go");
|
||||
Ok(config.unwrap())
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
};
|
||||
let config = config?;
|
||||
let max_nonce = data
|
||||
.db
|
||||
.get_max_nonce_for_level(&payload.key, config.difficulty_factor)
|
||||
.await?;
|
||||
data.stats.record_fetch(&data, &payload.key).await?;
|
||||
|
||||
// match res.exists {
|
||||
// Some(true) => {
|
||||
// match data.captcha.get_pow(payload.key.clone()).await {
|
||||
// Ok(Some(config)) => {
|
||||
// record_fetch(&payload.key, &data.db).await;
|
||||
// Ok(HttpResponse::Ok().json(config))
|
||||
// }
|
||||
// Ok(None) => {
|
||||
// init_mcaptcha(&data, &payload.key).await?;
|
||||
// let config = data
|
||||
// .captcha
|
||||
// .get_pow(payload.key.clone())
|
||||
// .await
|
||||
// .expect("mcaptcha should be initialized and ready to go");
|
||||
// // background it. would require data::Data to be static
|
||||
// // to satidfy lifetime
|
||||
// record_fetch(&payload.key, &data.db).await;
|
||||
// Ok(HttpResponse::Ok().json(config))
|
||||
// }
|
||||
// Err(e) => Err(e.into()),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Some(false) => Err(ServiceError::TokenNotFound),
|
||||
// None => Err(ServiceError::TokenNotFound),
|
||||
// }
|
||||
let config = ApiPoWConfig {
|
||||
string: config.string,
|
||||
difficulty_factor: config.difficulty_factor,
|
||||
salt: config.salt,
|
||||
max_recorded_nonce: max_nonce,
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(config))
|
||||
}
|
||||
/// Call this when [MCaptcha][libmcaptcha::MCaptcha] is not in master.
|
||||
///
|
||||
|
|
|
@ -65,6 +65,7 @@ pub async fn verify_pow(
|
|||
let payload = payload.into_inner();
|
||||
let worker_type = payload.worker_type.clone();
|
||||
let time = payload.time;
|
||||
let nonce = payload.nonce;
|
||||
let (res, difficulty_factor) = data.captcha.verify_pow(payload.into(), ip).await?;
|
||||
data.stats.record_solve(&data, &key).await?;
|
||||
if let (Some(time), Some(worker_type)) = (time, worker_type) {
|
||||
|
@ -75,6 +76,9 @@ pub async fn verify_pow(
|
|||
};
|
||||
data.db.analysis_save(&key, &analytics).await?;
|
||||
}
|
||||
data.db
|
||||
.update_max_nonce_for_level(&key, difficulty_factor, nonce as u32)
|
||||
.await?;
|
||||
let payload = ValidationToken { token: res };
|
||||
Ok(HttpResponse::Ok().json(payload))
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ use super::mcaptcha::routes::Captcha;
|
|||
use super::meta::routes::Meta;
|
||||
use super::notifications::routes::Notifications;
|
||||
use super::pow::routes::PoW;
|
||||
use super::stats::routes::Stats;
|
||||
use super::survey::routes::Survey;
|
||||
|
||||
pub const ROUTES: Routes = Routes::new();
|
||||
|
||||
|
@ -20,7 +22,9 @@ pub struct Routes {
|
|||
pub captcha: Captcha,
|
||||
pub meta: Meta,
|
||||
pub pow: PoW,
|
||||
pub survey: Survey,
|
||||
pub notifications: Notifications,
|
||||
pub stats: Stats,
|
||||
}
|
||||
|
||||
impl Routes {
|
||||
|
@ -32,6 +36,8 @@ impl Routes {
|
|||
meta: Meta::new(),
|
||||
pow: PoW::new(),
|
||||
notifications: Notifications::new(),
|
||||
survey: Survey::new(),
|
||||
stats: Stats::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
252
src/api/v1/stats.rs
Normal file
252
src/api/v1/stats.rs
Normal file
|
@ -0,0 +1,252 @@
|
|||
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use derive_builder::Builder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||
pub struct BuildDetails {
|
||||
pub version: &'static str,
|
||||
pub git_commit_hash: &'static str,
|
||||
}
|
||||
|
||||
pub mod routes {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Stats {
|
||||
pub percentile_benches: &'static str,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
percentile_benches: "/api/v1/stats/analytics/percentile",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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?;
|
||||
|
||||
if count == 0 {
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
difficulty_factor: None,
|
||||
}));
|
||||
}
|
||||
|
||||
if count < 2 {
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
difficulty_factor: None,
|
||||
}));
|
||||
}
|
||||
|
||||
let location = ((count - 1) as f64 * (payload.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,
|
||||
location.floor() as u32,
|
||||
)
|
||||
.await?,
|
||||
data.db
|
||||
.stats_get_entry_at_location_for_time_limit_asc(
|
||||
payload.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 {
|
||||
difficulty_factor: Some(res),
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if let Some(base) = data
|
||||
.db
|
||||
.stats_get_entry_at_location_for_time_limit_asc(
|
||||
payload.time,
|
||||
location.floor() as u32,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let res = base as u32;
|
||||
|
||||
return Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
difficulty_factor: Some(res),
|
||||
}));
|
||||
}
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(PercentileResp {
|
||||
difficulty_factor: None,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||
/// Health check return datatype
|
||||
pub struct PercentileReq {
|
||||
time: u32,
|
||||
percentile: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||
/// Health check return datatype
|
||||
pub struct PercentileResp {
|
||||
difficulty_factor: Option<u32>,
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(percentile_benches);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::{http::StatusCode, test, App};
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::services;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn stats_bench_work_pg() {
|
||||
let data = crate::tests::pg::get_data().await;
|
||||
stats_bench_work(data).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn stats_bench_work_maria() {
|
||||
let data = crate::tests::maria::get_data().await;
|
||||
stats_bench_work(data).await;
|
||||
}
|
||||
|
||||
async fn stats_bench_work(data: ArcData) {
|
||||
use crate::tests::*;
|
||||
|
||||
const NAME: &str = "benchstatsuesr";
|
||||
const EMAIL: &str = "benchstatsuesr@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 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;
|
||||
|
||||
assert!(resp.difficulty_factor.is_none());
|
||||
|
||||
let msg = PercentileReq {
|
||||
time: 2,
|
||||
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;
|
||||
|
||||
assert_eq!(resp.difficulty_factor.unwrap(), 2);
|
||||
|
||||
let msg = PercentileReq {
|
||||
time: 5,
|
||||
percentile: 90.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_eq!(resp.difficulty_factor.unwrap(), 4);
|
||||
delete_user(&data, NAME).await;
|
||||
}
|
||||
}
|
255
src/api/v1/survey.rs
Normal file
255
src/api/v1/survey.rs
Normal file
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use actix_web::web::ServiceConfig;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
pub fn services(cfg: &mut ServiceConfig) {
|
||||
cfg.service(download);
|
||||
cfg.service(secret);
|
||||
}
|
||||
|
||||
pub mod routes {
|
||||
pub struct Survey {
|
||||
pub download: &'static str,
|
||||
pub secret: &'static str,
|
||||
}
|
||||
|
||||
impl Survey {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
download: "/api/v1/survey/takeout/{survey_id}/get",
|
||||
secret: "/api/v1/survey/secret",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_download_route(&self, survey_id: &str, page: usize) -> String {
|
||||
format!(
|
||||
"{}?page={}",
|
||||
self.download.replace("{survey_id}", survey_id),
|
||||
page
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Page {
|
||||
pub page: usize,
|
||||
}
|
||||
|
||||
/// emits build details of the bninary
|
||||
#[my_codegen::get(path = "crate::V1_API_ROUTES.survey.download")]
|
||||
async fn download(
|
||||
data: AppData,
|
||||
page: web::Query<Page>,
|
||||
psuedo_id: web::Path<uuid::Uuid>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
const LIMIT: usize = 50;
|
||||
let offset = LIMIT as isize * ((page.page as isize) - 1);
|
||||
let offset = if offset < 0 { 0 } else { offset };
|
||||
let psuedo_id = psuedo_id.into_inner();
|
||||
let campaign_id = data
|
||||
.db
|
||||
.analytics_get_capmaign_id_from_psuedo_id(&psuedo_id.to_string())
|
||||
.await?;
|
||||
let data = data
|
||||
.db
|
||||
.analytics_fetch(&campaign_id, LIMIT, offset as usize)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().json(data))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SurveySecretUpload {
|
||||
secret: String,
|
||||
auth_token: String,
|
||||
}
|
||||
|
||||
/// mCaptcha/survey upload secret route
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.survey.secret")]
|
||||
async fn secret(
|
||||
data: AppData,
|
||||
payload: web::Json<SurveySecretUpload>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
match data.survey_secrets.get(&payload.auth_token) {
|
||||
Some(survey_instance_url) => {
|
||||
let payload = payload.into_inner();
|
||||
data.survey_secrets.set(survey_instance_url, payload.secret);
|
||||
data.survey_secrets.rm(&payload.auth_token);
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
None => Err(ServiceError::WrongPassword),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use actix_web::{http::StatusCode, test, App};
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::mcaptcha::get_random;
|
||||
use crate::tests::*;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn survey_works_pg() {
|
||||
let data = crate::tests::pg::get_data().await;
|
||||
survey_registration_works(data.clone()).await;
|
||||
survey_works(data).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn survey_works_maria() {
|
||||
let data = crate::tests::maria::get_data().await;
|
||||
survey_registration_works(data.clone()).await;
|
||||
survey_works(data).await;
|
||||
}
|
||||
|
||||
pub async fn survey_registration_works(data: ArcData) {
|
||||
let data = &data;
|
||||
let app = get_app!(data).await;
|
||||
|
||||
let survey_instance_url = "http://survey_registration_works.survey.example.org";
|
||||
|
||||
let key = get_random(20);
|
||||
|
||||
let msg = SurveySecretUpload {
|
||||
auth_token: key.clone(),
|
||||
secret: get_random(32),
|
||||
};
|
||||
|
||||
// should fail with ServiceError::WrongPassword since auth token is not loaded into
|
||||
// keystore
|
||||
bad_post_req_test_no_auth(
|
||||
data,
|
||||
V1_API_ROUTES.survey.secret,
|
||||
&msg,
|
||||
errors::ServiceError::WrongPassword,
|
||||
)
|
||||
.await;
|
||||
|
||||
// load auth token into key store, should succeed
|
||||
data.survey_secrets
|
||||
.set(key.clone(), survey_instance_url.to_owned());
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&msg, V1_API_ROUTES.survey.secret).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
// uploaded secret must be in keystore
|
||||
assert_eq!(
|
||||
data.survey_secrets.get(survey_instance_url).unwrap(),
|
||||
msg.secret
|
||||
);
|
||||
|
||||
// should fail since mCaptcha/survey secret upload auth tokens are single-use
|
||||
bad_post_req_test_no_auth(
|
||||
data,
|
||||
V1_API_ROUTES.survey.secret,
|
||||
&msg,
|
||||
errors::ServiceError::WrongPassword,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn survey_works(data: ArcData) {
|
||||
const NAME: &str = "survetuseranalytics";
|
||||
const PASSWORD: &str = "longpassworddomain";
|
||||
const EMAIL: &str = "survetuseranalytics@a.com";
|
||||
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 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 0..60 {
|
||||
println!("[{i}] Saving analytics");
|
||||
let analytics = db_core::CreatePerformanceAnalytics {
|
||||
time: 0,
|
||||
difficulty_factor: 0,
|
||||
worker_type: "wasm".into(),
|
||||
};
|
||||
data.db.analysis_save(&key.key, &analytics).await.unwrap();
|
||||
}
|
||||
|
||||
for p in 1..3 {
|
||||
let download_rotue = V1_API_ROUTES.survey.get_download_route(&psuedo_id, p);
|
||||
println!("page={p}, download={download_rotue}");
|
||||
|
||||
let download_req = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get().uri(&download_rotue).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(download_req.status(), StatusCode::OK);
|
||||
let analytics: Vec<db_core::PerformanceAnalytics> =
|
||||
test::read_body_json(download_req).await;
|
||||
if p == 1 {
|
||||
assert_eq!(analytics.len(), 50);
|
||||
} else if p == 2 {
|
||||
assert_eq!(analytics.len(), 10);
|
||||
} else {
|
||||
assert_eq!(analytics.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
let download_rotue = V1_API_ROUTES.survey.get_download_route(&psuedo_id, 0);
|
||||
data.db
|
||||
.analytics_delete_all_records_for_campaign(&key.key)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let download_req = test::call_service(
|
||||
&app,
|
||||
test::TestRequest::get().uri(&download_rotue).to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(download_req.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
}
|
20
src/data.rs
20
src/data.rs
|
@ -4,8 +4,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
//! App data: redis cache, database connections, etc.
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::prelude::*;
|
||||
use argon2_creds::{Config, ConfigBuilder, PasswordPolicy};
|
||||
|
@ -28,11 +30,17 @@ use libmcaptcha::{
|
|||
pow::Work,
|
||||
system::{System, SystemBuilder},
|
||||
};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::db::{self, BoxDB};
|
||||
use crate::errors::ServiceResult;
|
||||
use crate::settings::Settings;
|
||||
use crate::stats::{Dummy, Real, Stats};
|
||||
use crate::survey::SecretsStore;
|
||||
use crate::AppData;
|
||||
|
||||
macro_rules! enum_system_actor {
|
||||
($name:ident, $type:ident) => {
|
||||
|
@ -166,6 +174,8 @@ pub struct Data {
|
|||
pub settings: Settings,
|
||||
/// stats recorder
|
||||
pub stats: Box<dyn Stats>,
|
||||
/// survey secret store
|
||||
pub survey_secrets: SecretsStore,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
|
@ -180,7 +190,7 @@ impl Data {
|
|||
}
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
/// create new instance of app data
|
||||
pub async fn new(s: &Settings) -> Arc<Self> {
|
||||
pub async fn new(s: &Settings, survey_secrets: SecretsStore) -> Arc<Self> {
|
||||
let creds = Self::get_creds();
|
||||
let c = creds.clone();
|
||||
|
||||
|
@ -209,6 +219,7 @@ impl Data {
|
|||
mailer: Self::get_mailer(s),
|
||||
settings: s.clone(),
|
||||
stats,
|
||||
survey_secrets,
|
||||
};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
|
@ -242,6 +253,13 @@ impl Data {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn upload_survey_job(&self) -> ServiceResult<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
async fn register_survey(&self) -> ServiceResult<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Mailer data type AsyncSmtpTransport<Tokio1Executor>
|
||||
|
|
25
src/main.rs
25
src/main.rs
|
@ -30,6 +30,7 @@ mod routes;
|
|||
mod settings;
|
||||
mod static_assets;
|
||||
mod stats;
|
||||
mod survey;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
mod tests;
|
||||
|
@ -45,6 +46,7 @@ use static_assets::FileMap;
|
|||
pub use widget::WIDGET_ROUTES;
|
||||
|
||||
use crate::demo::DemoUser;
|
||||
use survey::SurveyClientTrait;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SETTINGS: Settings = Settings::new().unwrap();
|
||||
|
@ -93,7 +95,9 @@ pub type AppData = actix_web::web::Data<ArcData>;
|
|||
async fn main() -> std::io::Result<()> {
|
||||
use std::time::Duration;
|
||||
|
||||
env::set_var("RUST_LOG", "info");
|
||||
if env::var("RUST_LOG").is_err() {
|
||||
env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
||||
pretty_env_logger::init();
|
||||
info!(
|
||||
|
@ -102,7 +106,8 @@ async fn main() -> std::io::Result<()> {
|
|||
);
|
||||
|
||||
let settings = Settings::new().unwrap();
|
||||
let data = Data::new(&settings).await;
|
||||
let secrets = survey::SecretsStore::default();
|
||||
let data = Data::new(&settings, secrets.clone()).await;
|
||||
let data = actix_web::web::Data::new(data);
|
||||
|
||||
let mut demo_user: Option<DemoUser> = None;
|
||||
|
@ -115,6 +120,13 @@ async fn main() -> std::io::Result<()> {
|
|||
);
|
||||
}
|
||||
|
||||
let (mut survey_upload_tx, mut survey_upload_handle) = (None, None);
|
||||
if settings.survey.is_some() {
|
||||
let survey_runner_ctx = survey::Survey::new(data.clone());
|
||||
let (x, y) = survey_runner_ctx.start_job().await.unwrap();
|
||||
(survey_upload_tx, survey_upload_handle) = (Some(x), Some(y));
|
||||
}
|
||||
|
||||
let ip = settings.server.get_ip();
|
||||
println!("Starting server on: http://{ip}");
|
||||
|
||||
|
@ -139,9 +151,18 @@ async fn main() -> std::io::Result<()> {
|
|||
.run()
|
||||
.await?;
|
||||
|
||||
if let Some(survey_upload_tx) = survey_upload_tx {
|
||||
survey_upload_tx.send(()).unwrap();
|
||||
}
|
||||
|
||||
if let Some(demo_user) = demo_user {
|
||||
demo_user.abort();
|
||||
}
|
||||
|
||||
if let Some(survey_upload_handle) = survey_upload_handle {
|
||||
survey_upload_handle.await.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
231
src/settings.rs
231
src/settings.rs
|
@ -91,6 +91,13 @@ pub struct Redis {
|
|||
pub pool: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
pub struct Survey {
|
||||
pub nodes: Vec<url::Url>,
|
||||
pub rate_limit: u64,
|
||||
pub instance_root_url: Url,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||
pub struct Settings {
|
||||
pub debug: bool,
|
||||
|
@ -99,6 +106,7 @@ pub struct Settings {
|
|||
pub allow_registration: bool,
|
||||
pub allow_demo: bool,
|
||||
pub database: Database,
|
||||
pub survey: Option<Survey>,
|
||||
pub redis: Option<Redis>,
|
||||
pub server: Server,
|
||||
pub captcha: Captcha,
|
||||
|
@ -118,8 +126,8 @@ const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
|||
("database.pool", "MCAPTCHA_database_POOL"),
|
||||
|
||||
/* redis */
|
||||
("redis.url", "MCPATCHA_redis_URL"),
|
||||
("redis.pool", "MCPATCHA_redis_POOL"),
|
||||
("redis.url", "MCAPTCHA_redis_URL"),
|
||||
("redis.pool", "MCAPTCHA_redis_POOL"),
|
||||
|
||||
/* server */
|
||||
("server.port", "PORT"),
|
||||
|
@ -145,17 +153,52 @@ const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
|||
|
||||
|
||||
/* SMTP */
|
||||
("smtp.from", "MCPATCHA_smtp_FROM"),
|
||||
("smtp.reply", "MCPATCHA_smtp_REPLY"),
|
||||
("smtp.url", "MCPATCHA_smtp_URL"),
|
||||
("smtp.username", "MCPATCHA_smtp_USERNAME"),
|
||||
("smtp.password", "MCPATCHA_smtp_PASSWORD"),
|
||||
("smtp.port", "MCPATCHA_smtp_PORT"),
|
||||
("smtp.from", "MCAPTCHA_smtp_FROM"),
|
||||
("smtp.reply", "MCAPTCHA_smtp_REPLY"),
|
||||
("smtp.url", "MCAPTCHA_smtp_URL"),
|
||||
("smtp.username", "MCAPTCHA_smtp_USERNAME"),
|
||||
("smtp.password", "MCAPTCHA_smtp_PASSWORD"),
|
||||
("smtp.port", "MCAPTCHA_smtp_PORT"),
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
const DEPRECATED_ENV_VARS: [(&str, &str); 23] = [
|
||||
("debug", "MCAPTCHA_DEBUG"),
|
||||
("commercial", "MCAPTCHA_COMMERCIAL"),
|
||||
("source_code", "MCAPTCHA_SOURCE_CODE"),
|
||||
("allow_registration", "MCAPTCHA_ALLOW_REGISTRATION"),
|
||||
("allow_demo", "MCAPTCHA_ALLOW_DEMO"),
|
||||
("redis.pool", "MCAPTCHA_REDIS_POOL"),
|
||||
("redis.url", "MCAPTCHA_REDIS_URL"),
|
||||
("server.port", "MCAPTCHA_SERVER_PORT"),
|
||||
("server.ip", "MCAPTCHA_SERVER_IP"),
|
||||
("server.domain", "MCAPTCHA_SERVER_DOMAIN"),
|
||||
("server.cookie_secret", "MCAPTCHA_SERVER_COOKIE_SECRET"),
|
||||
("server.proxy_has_tls", "MCAPTCHA_SERVER_PROXY_HAS_TLS"),
|
||||
("captcha.salt", "MCAPTCHA_CAPTCHA_SALT"),
|
||||
("captcha.gc", "MCAPTCHA_CAPTCHA_GC"),
|
||||
(
|
||||
"captcha.default_difficulty_strategy.avg_traffic_difficulty",
|
||||
"MCAPTCHA_CAPTCHA_AVG_TRAFFIC_DIFFICULTY",
|
||||
),
|
||||
(
|
||||
"captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty",
|
||||
"MCAPTCHA_CAPTCHA_PEAK_TRAFFIC_DIFFICULTY",
|
||||
),
|
||||
(
|
||||
"captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty",
|
||||
"MCAPTCHA_CAPTCHA_BROKE_MY_SITE_TRAFFIC",
|
||||
),
|
||||
("smtp.from", "MCAPTCHA_SMTP_FROM"),
|
||||
("smtp.reply", "MCAPTCHA_SMTP_REPLY_TO"),
|
||||
("smtp.url", "MCAPTCHA_SMTP_URL"),
|
||||
("smtp.username", "MCAPTCHA_SMTP_USERNAME"),
|
||||
("smtp.password", "MCAPTCHA_SMTP_PASSWORD"),
|
||||
("smtp.port", "MCAPTCHA_SMTP_PORT"),
|
||||
];
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Settings {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
|
@ -210,6 +253,15 @@ impl Settings {
|
|||
}
|
||||
|
||||
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||
for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
|
||||
if let Ok(val) = env::var(env_var_name) {
|
||||
log::warn!(
|
||||
"Found {env_var_name}. {env_var_name} will be deprecated soon. Please see https://github.com/mCaptcha/mCaptcha/blob/master/docs/CONFIGURATION.md for latest environment variable names"
|
||||
);
|
||||
s = s.set_override(parameter, val).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for (parameter, env_var_name) in ENV_VAR_CONFIG.iter() {
|
||||
if let Ok(val) = env::var(env_var_name) {
|
||||
log::debug!(
|
||||
|
@ -240,7 +292,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn env_override_works() {
|
||||
fn deprecated_env_override_works() {
|
||||
use crate::tests::get_settings;
|
||||
let init_settings = get_settings();
|
||||
// so that it can be tested outside the macro (helper) too
|
||||
|
@ -249,6 +301,141 @@ mod tests {
|
|||
macro_rules! helper {
|
||||
|
||||
|
||||
($env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
|
||||
println!("Setting env var {} to {} for test", $env, $val);
|
||||
env::set_var($env, $val);
|
||||
new_settings = get_settings();
|
||||
assert_eq!(new_settings.$($param).+, $val_typed);
|
||||
assert_ne!(new_settings.$($param).+, init_settings.$($param).+);
|
||||
env::remove_var($env);
|
||||
};
|
||||
|
||||
|
||||
($env:expr, $val:expr, $($param:ident).+) => {
|
||||
helper!($env, $val.to_string(), $val, $($param).+);
|
||||
};
|
||||
}
|
||||
|
||||
/* top level */
|
||||
helper!("MCAPTCHA_DEBUG", !init_settings.debug, debug);
|
||||
helper!("MCAPTCHA_COMMERCIAL", !init_settings.commercial, commercial);
|
||||
helper!(
|
||||
"MCAPTCHA_ALLOW_REGISTRATION",
|
||||
!init_settings.allow_registration,
|
||||
allow_registration
|
||||
);
|
||||
helper!("MCAPTCHA_ALLOW_DEMO", !init_settings.allow_demo, allow_demo);
|
||||
|
||||
/* database_type */
|
||||
|
||||
/* redis.url */
|
||||
let env = "MCAPTCHA_REDIS_URL";
|
||||
let val = "redis://redis.example.org";
|
||||
println!("Setting env var {} to {} for test", env, val);
|
||||
env::set_var(env, val);
|
||||
new_settings = get_settings();
|
||||
assert_eq!(new_settings.redis.as_ref().unwrap().url, val);
|
||||
assert_ne!(
|
||||
new_settings.redis.as_ref().unwrap().url,
|
||||
init_settings.redis.as_ref().unwrap().url
|
||||
);
|
||||
env::remove_var(env);
|
||||
|
||||
/* redis.pool */
|
||||
let env = "MCAPTCHA_REDIS_POOL";
|
||||
let val = 999;
|
||||
println!("Setting env var {} to {} for test", env, val);
|
||||
env::set_var(env, val.to_string());
|
||||
new_settings = get_settings();
|
||||
assert_eq!(new_settings.redis.as_ref().unwrap().pool, val);
|
||||
assert_ne!(
|
||||
new_settings.redis.as_ref().unwrap().pool,
|
||||
init_settings.redis.as_ref().unwrap().pool
|
||||
);
|
||||
env::remove_var(env);
|
||||
|
||||
helper!("PORT", 0, server.port);
|
||||
helper!("MCAPTCHA_SERVER_DOMAIN", "example.org", server.domain);
|
||||
helper!(
|
||||
"MCAPTCHA_SERVER_COOKIE_SECRET",
|
||||
"dafasdfsdf",
|
||||
server.cookie_secret
|
||||
);
|
||||
helper!("MCAPTCHA_SERVER_IP", "9.9.9.9", server.ip);
|
||||
helper!("MCAPTCHA_SERVER_PROXY_HAS_TLS", true, server.proxy_has_tls);
|
||||
|
||||
/* captcha */
|
||||
|
||||
helper!("MCAPTCHA_CAPTCHA_SALT", "foobarasdfasdf", captcha.salt);
|
||||
helper!("MCAPTCHA_CAPTCHA_GC", 500, captcha.gc);
|
||||
helper!(
|
||||
"MCAPTCHA_captcha_RUNNERS",
|
||||
"500",
|
||||
Some(500),
|
||||
captcha.runners
|
||||
);
|
||||
|
||||
helper!(
|
||||
"MCAPTCHA_CAPTCHA_AVG_TRAFFIC_DIFFICULTY",
|
||||
999,
|
||||
captcha.default_difficulty_strategy.avg_traffic_difficulty
|
||||
);
|
||||
helper!(
|
||||
"MCAPTCHA_CAPTCHA_PEAK_TRAFFIC_DIFFICULTY",
|
||||
999,
|
||||
captcha
|
||||
.default_difficulty_strategy
|
||||
.peak_sustainable_traffic_difficulty
|
||||
);
|
||||
helper!(
|
||||
"MCAPTCHA_CAPTCHA_BROKE_MY_SITE_TRAFFIC",
|
||||
999,
|
||||
captcha
|
||||
.default_difficulty_strategy
|
||||
.broke_my_site_traffic_difficulty
|
||||
);
|
||||
|
||||
/* SMTP */
|
||||
|
||||
let vals = [
|
||||
"MCAPTCHA_SMTP_FROM",
|
||||
"MCAPTCHA_SMTP_REPLY_TO",
|
||||
"MCAPTCHA_SMTP_URL",
|
||||
"MCAPTCHA_SMTP_USERNAME",
|
||||
"MCAPTCHA_SMTP_PASSWORD",
|
||||
"MCAPTCHA_SMTP_PORT",
|
||||
];
|
||||
for env in vals.iter() {
|
||||
println!("Setting env var {} to {} for test", env, env);
|
||||
env::set_var(env, env);
|
||||
}
|
||||
|
||||
let port = 9999;
|
||||
env::set_var("MCAPTCHA_SMTP_PORT", port.to_string());
|
||||
|
||||
new_settings = get_settings();
|
||||
let smtp_new = new_settings.smtp.as_ref().unwrap();
|
||||
let smtp_old = init_settings.smtp.as_ref().unwrap();
|
||||
assert_eq!(smtp_new.from, "MCAPTCHA_SMTP_FROM");
|
||||
assert_eq!(smtp_new.reply, "MCAPTCHA_SMTP_REPLY_TO");
|
||||
assert_eq!(smtp_new.username, "MCAPTCHA_SMTP_USERNAME");
|
||||
assert_eq!(smtp_new.password, "MCAPTCHA_SMTP_PASSWORD");
|
||||
assert_eq!(smtp_new.port, port);
|
||||
assert_ne!(smtp_new, smtp_old);
|
||||
|
||||
for env in vals.iter() {
|
||||
env::remove_var(env);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_override_works() {
|
||||
use crate::tests::get_settings;
|
||||
let init_settings = get_settings();
|
||||
// so that it can be tested outside the macro (helper) too
|
||||
let mut new_settings;
|
||||
|
||||
macro_rules! helper {
|
||||
|
||||
|
||||
($env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
|
||||
|
@ -291,7 +478,7 @@ mod tests {
|
|||
/* redis */
|
||||
|
||||
/* redis.url */
|
||||
let env = "MCPATCHA_redis_URL";
|
||||
let env = "MCAPTCHA_redis_URL";
|
||||
let val = "redis://redis.example.org";
|
||||
println!("Setting env var {} to {} for test", env, val);
|
||||
env::set_var(env, val);
|
||||
|
@ -304,7 +491,7 @@ mod tests {
|
|||
env::remove_var(env);
|
||||
|
||||
/* redis.pool */
|
||||
let env = "MCPATCHA_redis_POOL";
|
||||
let env = "MCAPTCHA_redis_POOL";
|
||||
let val = 999;
|
||||
println!("Setting env var {} to {} for test", env, val);
|
||||
env::set_var(env, val.to_string());
|
||||
|
@ -355,12 +542,12 @@ mod tests {
|
|||
/* SMTP */
|
||||
|
||||
let vals = [
|
||||
"MCPATCHA_smtp_FROM",
|
||||
"MCPATCHA_smtp_REPLY",
|
||||
"MCPATCHA_smtp_URL",
|
||||
"MCPATCHA_smtp_USERNAME",
|
||||
"MCPATCHA_smtp_PASSWORD",
|
||||
"MCPATCHA_smtp_PORT",
|
||||
"MCAPTCHA_smtp_FROM",
|
||||
"MCAPTCHA_smtp_REPLY",
|
||||
"MCAPTCHA_smtp_URL",
|
||||
"MCAPTCHA_smtp_USERNAME",
|
||||
"MCAPTCHA_smtp_PASSWORD",
|
||||
"MCAPTCHA_smtp_PORT",
|
||||
];
|
||||
for env in vals.iter() {
|
||||
println!("Setting env var {} to {} for test", env, env);
|
||||
|
@ -368,15 +555,15 @@ mod tests {
|
|||
}
|
||||
|
||||
let port = 9999;
|
||||
env::set_var("MCPATCHA_smtp_PORT", port.to_string());
|
||||
env::set_var("MCAPTCHA_smtp_PORT", port.to_string());
|
||||
|
||||
new_settings = get_settings();
|
||||
let smtp_new = new_settings.smtp.as_ref().unwrap();
|
||||
let smtp_old = init_settings.smtp.as_ref().unwrap();
|
||||
assert_eq!(smtp_new.from, "MCPATCHA_smtp_FROM");
|
||||
assert_eq!(smtp_new.reply, "MCPATCHA_smtp_REPLY");
|
||||
assert_eq!(smtp_new.username, "MCPATCHA_smtp_USERNAME");
|
||||
assert_eq!(smtp_new.password, "MCPATCHA_smtp_PASSWORD");
|
||||
assert_eq!(smtp_new.from, "MCAPTCHA_smtp_FROM");
|
||||
assert_eq!(smtp_new.reply, "MCAPTCHA_smtp_REPLY");
|
||||
assert_eq!(smtp_new.username, "MCAPTCHA_smtp_USERNAME");
|
||||
assert_eq!(smtp_new.password, "MCAPTCHA_smtp_PASSWORD");
|
||||
assert_eq!(smtp_new.port, port);
|
||||
assert_ne!(smtp_new, smtp_old);
|
||||
|
||||
|
|
209
src/survey.rs
Normal file
209
src/survey.rs
Normal file
|
@ -0,0 +1,209 @@
|
|||
// Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::settings::Settings;
|
||||
use crate::AppData;
|
||||
use crate::V1_API_ROUTES;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait SurveyClientTrait {
|
||||
async fn start_job(&self) -> ServiceResult<(oneshot::Sender<()>, JoinHandle<()>)>;
|
||||
async fn schedule_upload_job(&self) -> ServiceResult<()>;
|
||||
async fn is_online(&self) -> ServiceResult<bool>;
|
||||
async fn register(&self) -> ServiceResult<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SecretsStore {
|
||||
store: Arc<RwLock<HashMap<String, String>>>,
|
||||
}
|
||||
|
||||
impl SecretsStore {
|
||||
pub fn get(&self, key: &str) -> Option<String> {
|
||||
let r = self.store.read().unwrap();
|
||||
r.get(key).map(|x| x.to_owned())
|
||||
}
|
||||
|
||||
pub fn rm(&self, key: &str) {
|
||||
let mut w = self.store.write().unwrap();
|
||||
w.remove(key);
|
||||
drop(w);
|
||||
}
|
||||
|
||||
pub fn set(&self, key: String, value: String) {
|
||||
let mut w = self.store.write().unwrap();
|
||||
w.insert(key, value);
|
||||
drop(w);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Survey {
|
||||
client: Client,
|
||||
app_ctx: AppData,
|
||||
}
|
||||
impl Survey {
|
||||
pub fn new(app_ctx: AppData) -> Self {
|
||||
if app_ctx.settings.survey.is_none() {
|
||||
panic!("Survey uploader shouldn't be initialized it isn't configured, please report this bug")
|
||||
}
|
||||
Survey {
|
||||
client: Client::new(),
|
||||
app_ctx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SurveyClientTrait for Survey {
|
||||
async fn start_job(&self) -> ServiceResult<(oneshot::Sender<()>, JoinHandle<()>)> {
|
||||
fn can_run(rx: &mut oneshot::Receiver<()>) -> bool {
|
||||
match rx.try_recv() {
|
||||
Err(oneshot::error::TryRecvError::Empty) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
let (tx, mut rx) = oneshot::channel();
|
||||
let this = self.clone();
|
||||
let mut register = false;
|
||||
let fut = async move {
|
||||
loop {
|
||||
if !can_run(&mut rx) {
|
||||
log::info!("Stopping survey uploads");
|
||||
break;
|
||||
}
|
||||
|
||||
if !register {
|
||||
loop {
|
||||
if this.is_online().await.unwrap() {
|
||||
this.register().await.unwrap();
|
||||
register = true;
|
||||
break;
|
||||
} else {
|
||||
sleep(Duration::new(1, 0)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..this.app_ctx.settings.survey.as_ref().unwrap().rate_limit {
|
||||
if !can_run(&mut rx) {
|
||||
log::info!("Stopping survey uploads");
|
||||
break;
|
||||
}
|
||||
sleep(Duration::new(1, 0)).await;
|
||||
}
|
||||
let _ = this.schedule_upload_job().await;
|
||||
|
||||
// for url in this.app_ctx.settings.survey.as_ref().unwrap().nodes.iter() {
|
||||
// if !can_run(&mut rx) {
|
||||
// log::info!("Stopping survey uploads");
|
||||
// break;
|
||||
// }
|
||||
// log::info!("Uploading to survey instance {}", url);
|
||||
// }
|
||||
}
|
||||
};
|
||||
let handle = tokio::spawn(fut);
|
||||
Ok((tx, handle))
|
||||
}
|
||||
async fn is_online(&self) -> ServiceResult<bool> {
|
||||
let res = self
|
||||
.client
|
||||
.get(format!(
|
||||
"http://{}{}",
|
||||
self.app_ctx.settings.server.get_ip(),
|
||||
V1_API_ROUTES.meta.health
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.status() == 200)
|
||||
}
|
||||
|
||||
async fn schedule_upload_job(&self) -> ServiceResult<()> {
|
||||
log::debug!("Running upload job");
|
||||
#[derive(Serialize)]
|
||||
struct Secret {
|
||||
secret: String,
|
||||
}
|
||||
let mut page = 0;
|
||||
loop {
|
||||
let psuedo_ids = self.app_ctx.db.analytics_get_all_psuedo_ids(page).await?;
|
||||
if psuedo_ids.is_empty() {
|
||||
log::debug!("upload job complete, no more IDs to upload");
|
||||
break;
|
||||
}
|
||||
for id in psuedo_ids {
|
||||
for url in self.app_ctx.settings.survey.as_ref().unwrap().nodes.iter() {
|
||||
if let Some(secret) = self.app_ctx.survey_secrets.get(url.as_str()) {
|
||||
let payload = Secret { secret };
|
||||
|
||||
log::info!("Uploading to survey instance {} campaign {id}", url);
|
||||
let mut url = url.clone();
|
||||
url.set_path(&format!("/mcaptcha/api/v1/{id}/upload"));
|
||||
let resp =
|
||||
self.client.post(url).json(&payload).send().await.unwrap();
|
||||
println!("{}", resp.text().await.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn register(&self) -> ServiceResult<()> {
|
||||
#[derive(Serialize)]
|
||||
struct MCaptchaInstance {
|
||||
url: url::Url,
|
||||
auth_token: String,
|
||||
}
|
||||
|
||||
let this_instance_url = self
|
||||
.app_ctx
|
||||
.settings
|
||||
.survey
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.instance_root_url
|
||||
.clone();
|
||||
for url in self.app_ctx.settings.survey.as_ref().unwrap().nodes.iter() {
|
||||
// mCaptcha/survey must send this token while uploading secret to authenticate itself
|
||||
// this token must be sent to mCaptcha/survey with the registration payload
|
||||
let secret_upload_auth_token = crate::api::v1::mcaptcha::get_random(20);
|
||||
|
||||
let payload = MCaptchaInstance {
|
||||
url: this_instance_url.clone(),
|
||||
auth_token: secret_upload_auth_token.clone(),
|
||||
};
|
||||
|
||||
// SecretsStore will store auth tokens generated by both mCaptcha/mCaptcha and
|
||||
// mCaptcha/survey
|
||||
//
|
||||
// Storage schema:
|
||||
// - mCaptcha/mCaptcha generated auth token: (<auth_token>, <survey_instance_url>)
|
||||
// - mCaptcha/survey generated auth token (<survey_instance_url>, <auth_token)
|
||||
self.app_ctx
|
||||
.survey_secrets
|
||||
.set(secret_upload_auth_token, url.to_string());
|
||||
let mut url = url.clone();
|
||||
url.set_path("/mcaptcha/api/v1/register");
|
||||
let resp = self.client.post(url).json(&payload).send().await.unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ use crate::api::v1::mcaptcha::create::CreateCaptcha;
|
|||
use crate::api::v1::mcaptcha::create::MCaptchaDetails;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::errors::*;
|
||||
use crate::survey::SecretsStore;
|
||||
use crate::ArcData;
|
||||
|
||||
pub fn get_settings() -> Settings {
|
||||
|
@ -28,41 +29,69 @@ pub fn get_settings() -> Settings {
|
|||
pub mod pg {
|
||||
use std::env;
|
||||
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
|
||||
use crate::api::v1::mcaptcha::get_random;
|
||||
use crate::data::Data;
|
||||
use crate::settings::*;
|
||||
use crate::survey::SecretsStore;
|
||||
use crate::ArcData;
|
||||
|
||||
use super::get_settings;
|
||||
|
||||
pub async fn get_data() -> ArcData {
|
||||
let url = env::var("POSTGRES_DATABASE_URL").unwrap();
|
||||
|
||||
let mut parsed = url::Url::parse(&url).unwrap();
|
||||
parsed.set_path(&get_random(16));
|
||||
let url = parsed.to_string();
|
||||
|
||||
if sqlx::Postgres::database_exists(&url).await.unwrap() {
|
||||
sqlx::Postgres::drop_database(&url).await.unwrap();
|
||||
}
|
||||
sqlx::Postgres::create_database(&url).await.unwrap();
|
||||
|
||||
let mut settings = get_settings();
|
||||
settings.captcha.runners = Some(1);
|
||||
settings.database.url = url.clone();
|
||||
settings.database.database_type = DBType::Postgres;
|
||||
settings.database.pool = 2;
|
||||
|
||||
Data::new(&settings).await
|
||||
Data::new(&settings, SecretsStore::default()).await
|
||||
}
|
||||
}
|
||||
pub mod maria {
|
||||
use std::env;
|
||||
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
|
||||
use crate::api::v1::mcaptcha::get_random;
|
||||
use crate::data::Data;
|
||||
use crate::settings::*;
|
||||
use crate::survey::SecretsStore;
|
||||
use crate::ArcData;
|
||||
|
||||
use super::get_settings;
|
||||
|
||||
pub async fn get_data() -> ArcData {
|
||||
let url = env::var("MARIA_DATABASE_URL").unwrap();
|
||||
|
||||
let mut parsed = url::Url::parse(&url).unwrap();
|
||||
parsed.set_path(&get_random(16));
|
||||
let url = parsed.to_string();
|
||||
|
||||
if sqlx::MySql::database_exists(&url).await.unwrap() {
|
||||
sqlx::MySql::drop_database(&url).await.unwrap();
|
||||
}
|
||||
sqlx::MySql::create_database(&url).await.unwrap();
|
||||
|
||||
let mut settings = get_settings();
|
||||
settings.captcha.runners = Some(1);
|
||||
settings.database.url = url.clone();
|
||||
settings.database.database_type = DBType::Maria;
|
||||
settings.database.pool = 2;
|
||||
|
||||
Data::new(&settings).await
|
||||
Data::new(&settings, SecretsStore::default()).await
|
||||
}
|
||||
}
|
||||
//pub async fn get_data() -> ArcData {
|
||||
|
@ -181,6 +210,26 @@ pub async fn signin(
|
|||
(creds, signin_resp)
|
||||
}
|
||||
|
||||
/// pub duplicate test
|
||||
pub async fn bad_post_req_test_no_auth<T: Serialize>(
|
||||
data: &ArcData,
|
||||
url: &str,
|
||||
payload: &T,
|
||||
err: ServiceError,
|
||||
) {
|
||||
let app = get_app!(data).await;
|
||||
|
||||
let resp = test::call_service(&app, post_request!(&payload, url).to_request()).await;
|
||||
if resp.status() != err.status_code() {
|
||||
let resp_err: ErrorToResponse = test::read_body_json(resp).await;
|
||||
panic!("error {}", resp_err.error);
|
||||
}
|
||||
assert_eq!(resp.status(), err.status_code());
|
||||
let resp_err: ErrorToResponse = test::read_body_json(resp).await;
|
||||
//println!("{}", txt.error);
|
||||
assert_eq!(resp_err.error, format!("{}", err));
|
||||
}
|
||||
|
||||
/// pub duplicate test
|
||||
pub async fn bad_post_req_test<T: Serialize>(
|
||||
data: &ArcData,
|
||||
|
|
29
static/openapi/.gitignore
vendored
Normal file
29
static/openapi/.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
node_modules
|
||||
.idea
|
||||
.vscode
|
||||
.deps_check
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.nyc_output
|
||||
npm-debug.log*
|
||||
.eslintcache
|
||||
*.iml
|
||||
selenium-debug.log
|
||||
chromedriver.log
|
||||
test/e2e/db.json
|
||||
docs/_book
|
||||
dev-helpers/examples
|
||||
|
||||
# dist
|
||||
flavors/**/dist/*
|
||||
/lib
|
||||
/es
|
||||
dist/log*
|
||||
/swagger-ui-*.tgz
|
||||
|
||||
# Cypress
|
||||
test/e2e-cypress/screenshots
|
||||
test/e2e-cypress/videos
|
|
@ -1,5 +1,5 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
@ -25,22 +25,22 @@
|
|||
<script src="./swagger-ui-bundle.js" charset="UTF-8"></script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"></script>
|
||||
<script src="./swagger-initializer.js" charset="UTF-8"></script>
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "./openapi.yaml",
|
||||
dom_id: "#swagger-ui",
|
||||
deepLinking: true,
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
||||
layout: "StandaloneLayout",
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "/docs/openapi.yaml",
|
||||
dom_id: "#swagger-ui",
|
||||
deepLinking: true,
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
||||
layout: "StandaloneLayout",
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
@ -1,99 +1,283 @@
|
|||
ramda: 361.65 KB (6.14%)
|
||||
core-js-pure: 300.89 KB (5.11%)
|
||||
ramda-adjunct: 255.42 KB (4.34%)
|
||||
lodash: 253.56 KB (4.30%)
|
||||
@swagger-api/apidom-ns-openapi-3-0: 204.6 KB (3.47%)
|
||||
autolinker: 203.32 KB (3.45%)
|
||||
swagger-client: 159.41 KB (2.71%)
|
||||
is-plain-object: 758 B (0.464%)
|
||||
<self>: 158.67 KB (99.5%)
|
||||
@swagger-api/apidom-ns-openapi-3-1: 154.72 KB (2.63%)
|
||||
immutable: 139.01 KB (2.36%)
|
||||
remarkable: 125.56 KB (2.13%)
|
||||
react-dom: 119.19 KB (2.02%)
|
||||
highlight.js: 111.85 KB (1.90%)
|
||||
js-yaml: 105.01 KB (1.78%)
|
||||
readable-stream: 96.66 KB (1.64%)
|
||||
@swagger-api/apidom-reference: 84.72 KB (1.44%)
|
||||
dompurify: 61.27 KB (1.04%)
|
||||
@swagger-api/apidom-ns-json-schema-draft-4: 58.11 KB (0.987%)
|
||||
minim: 57.35 KB (0.974%)
|
||||
buffer: 56.99 KB (0.967%)
|
||||
@swagger-api/apidom-core: 47.72 KB (0.810%)
|
||||
@swagger-api/apidom-ast: 44.47 KB (0.755%)
|
||||
react-redux: 43.7 KB (0.742%)
|
||||
react-syntax-highlighter: 38.01 KB (0.645%)
|
||||
url: 37.24 KB (0.632%)
|
||||
punycode: 14.33 KB (38.5%)
|
||||
<self>: 22.92 KB (61.5%)
|
||||
fast-json-patch: 31.89 KB (0.541%)
|
||||
redux: 27.59 KB (0.468%)
|
||||
qs: 26.61 KB (0.452%)
|
||||
sha.js: 18.57 KB (0.315%)
|
||||
object-inspect: 18.15 KB (0.308%)
|
||||
url-parse: 16.23 KB (0.276%)
|
||||
events: 14.54 KB (0.247%)
|
||||
cross-fetch: 14.31 KB (0.243%)
|
||||
tslib: 14.21 KB (0.241%)
|
||||
@babel/runtime-corejs3: 13.24 KB (0.225%)
|
||||
get-intrinsic: 13.01 KB (0.221%)
|
||||
reselect: 12.51 KB (0.212%)
|
||||
zenscroll: 12.31 KB (0.209%)
|
||||
react-debounce-input: 11.95 KB (0.203%)
|
||||
react-immutable-proptypes: 11.82 KB (0.201%)
|
||||
ret: 10.82 KB (0.184%)
|
||||
lodash.debounce: 10.53 KB (0.179%)
|
||||
unraw: 10.27 KB (0.174%)
|
||||
string_decoder: 9.24 KB (0.157%)
|
||||
short-unique-id: 8.29 KB (0.141%)
|
||||
xml: 7.39 KB (0.125%)
|
||||
react-copy-to-clipboard: 7.33 KB (0.124%)
|
||||
traverse: 7.15 KB (0.121%)
|
||||
react: 6.48 KB (0.110%)
|
||||
@babel/runtime: 6.29 KB (0.107%)
|
||||
randexp: 6.15 KB (0.104%)
|
||||
react-immutable-pure-component: 6.01 KB (0.102%)
|
||||
redux-immutable: 5.43 KB (0.0922%)
|
||||
process: 5.29 KB (0.0898%)
|
||||
cookie: 5.1 KB (0.0866%)
|
||||
scheduler: 4.91 KB (0.0834%)
|
||||
drange: 4.8 KB (0.0815%)
|
||||
lowlight: 4.42 KB (0.0751%)
|
||||
deep-extend: 4.19 KB (0.0712%)
|
||||
deepmerge: 3.95 KB (0.0671%)
|
||||
base64-js: 3.84 KB (0.0652%)
|
||||
stream-browserify: 3.76 KB (0.0638%)
|
||||
@swagger-api/apidom-json-pointer: 3.68 KB (0.0625%)
|
||||
side-channel: 3.31 KB (0.0562%)
|
||||
copy-to-clipboard: 3.29 KB (0.0558%)
|
||||
format: 3.26 KB (0.0553%)
|
||||
stampit: 3.16 KB (0.0536%)
|
||||
css.escape: 3.08 KB (0.0523%)
|
||||
serialize-error: 2.93 KB (0.0497%)
|
||||
hoist-non-react-statics: 2.68 KB (0.0455%)
|
||||
prop-types: 2.6 KB (0.0441%)
|
||||
use-sync-external-store: 2.59 KB (0.0440%)
|
||||
querystringify: 2.5 KB (0.0425%)
|
||||
react-is: 2.48 KB (0.0422%)
|
||||
xml-but-prettier: 2.17 KB (0.0368%)
|
||||
has-symbols: 2.13 KB (0.0362%)
|
||||
ieee754: 2.1 KB (0.0357%)
|
||||
object-assign: 2.06 KB (0.0349%)
|
||||
call-bind: 1.68 KB (0.0285%)
|
||||
safe-buffer: 1.63 KB (0.0277%)
|
||||
util-deprecate: 1.58 KB (0.0268%)
|
||||
function-bind: 1.55 KB (0.0263%)
|
||||
randombytes: 1.54 KB (0.0261%)
|
||||
js-file-download: 1.52 KB (0.0258%)
|
||||
classnames: 1.38 KB (0.0235%)
|
||||
@braintree/sanitize-url: 1.38 KB (0.0234%)
|
||||
repeat-string: 1.18 KB (0.0201%)
|
||||
toggle-selection: 780 B (0.0129%)
|
||||
inherits: 753 B (0.0125%)
|
||||
requires-port: 753 B (0.0125%)
|
||||
fault: 691 B (0.0115%)
|
||||
formdata-node: 259 B (0.00429%)
|
||||
has-proto: 197 B (0.00327%)
|
||||
has: 129 B (0.00214%)
|
||||
object-inspect|.: 15 B (0.000249%)
|
||||
<self>: 2.26 MB (39.2%)
|
||||
.pnpm/ramda@0.29.1: 361.78 KB (5.90%)
|
||||
ramda: 361.78 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/ramda-adjunct@4.1.1_ramda@0.29.1: 257.62 KB (4.20%)
|
||||
ramda-adjunct: 257.62 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/lodash@4.17.21: 253.56 KB (4.14%)
|
||||
lodash: 253.56 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-ns-openapi-3-0@0.80.0: 203.35 KB (3.32%)
|
||||
@swagger-api/apidom-ns-openapi-3-0: 203.35 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/autolinker@3.16.2: 203.32 KB (3.32%)
|
||||
autolinker: 203.32 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/swagger-client@3.23.1: 166.55 KB (2.72%)
|
||||
swagger-client: 166.55 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-ns-openapi-3-1@0.80.0: 151.35 KB (2.47%)
|
||||
@swagger-api/apidom-ns-openapi-3-1: 151.35 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/immutable@3.8.2: 139.01 KB (2.27%)
|
||||
immutable: 139.01 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/remarkable@2.0.1: 125.56 KB (2.05%)
|
||||
remarkable: 125.56 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-dom@17.0.2_react@17.0.2: 119.19 KB (1.94%)
|
||||
react-dom: 119.19 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/highlight.js@10.7.3: 111.85 KB (1.82%)
|
||||
highlight.js: 111.85 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/js-yaml@4.1.0: 105.01 KB (1.71%)
|
||||
js-yaml: 105.01 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/readable-stream@3.6.2: 96.66 KB (1.58%)
|
||||
readable-stream: 96.66 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/core-js-pure@3.33.1: 82.98 KB (1.35%)
|
||||
core-js-pure: 82.98 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-reference@0.80.0: 81.46 KB (1.33%)
|
||||
@swagger-api/apidom-reference: 81.46 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/dompurify@3.0.6: 62.88 KB (1.03%)
|
||||
dompurify: 62.88 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/minim@0.23.8: 57.35 KB (0.935%)
|
||||
minim: 57.35 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/buffer@6.0.3: 56.99 KB (0.929%)
|
||||
buffer: 56.99 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-ns-json-schema-draft-4@0.80.0: 54.6 KB (0.890%)
|
||||
@swagger-api/apidom-ns-json-schema-draft-4: 54.6 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-redux@8.1.3_react-dom@17.0.2_react@17.0.2_redux@4.2.1: 48.26 KB (0.787%)
|
||||
react-redux: 48.26 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-core@0.80.0: 48.12 KB (0.785%)
|
||||
@swagger-api/apidom-core: 48.12 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-ast@0.80.0: 46.52 KB (0.759%)
|
||||
@swagger-api/apidom-ast: 46.52 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-syntax-highlighter@15.5.0_react@17.0.2: 40.15 KB (0.655%)
|
||||
react-syntax-highlighter: 40.15 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/fast-json-patch@3.1.1: 31.89 KB (0.520%)
|
||||
fast-json-patch: 31.89 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/redux@4.2.1: 26.64 KB (0.434%)
|
||||
redux: 26.64 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/qs@6.11.2: 26.23 KB (0.428%)
|
||||
qs: 26.23 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/short-unique-id@5.0.3: 18.88 KB (0.308%)
|
||||
short-unique-id: 18.88 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/sha.js@2.4.11: 18.57 KB (0.303%)
|
||||
sha.js: 18.57 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/object-inspect@1.13.1: 18.45 KB (0.301%)
|
||||
object-inspect: 18.44 KB (99.9%)
|
||||
<self>: 15 B (0.0794%)
|
||||
.pnpm/url-parse@1.5.10: 16.23 KB (0.265%)
|
||||
url-parse: 16.23 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/tslib@2.6.2: 15.87 KB (0.259%)
|
||||
tslib: 15.87 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/events@3.3.0: 14.54 KB (0.237%)
|
||||
events: 14.54 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/get-intrinsic@1.2.2: 13.01 KB (0.212%)
|
||||
get-intrinsic: 13.01 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/zenscroll@4.0.2: 12.31 KB (0.201%)
|
||||
zenscroll: 12.31 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-debounce-input@3.3.0_react@17.0.2: 11.95 KB (0.195%)
|
||||
react-debounce-input: 11.95 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-immutable-proptypes@2.2.0_immutable@3.8.2: 11.82 KB (0.193%)
|
||||
react-immutable-proptypes: 11.82 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/ret@0.2.2: 10.82 KB (0.176%)
|
||||
ret: 10.82 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/lodash.debounce@4.0.8: 10.53 KB (0.172%)
|
||||
lodash.debounce: 10.53 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/unraw@3.0.0: 9.9 KB (0.161%)
|
||||
unraw: 9.9 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/string_decoder@1.3.0: 9.24 KB (0.151%)
|
||||
string_decoder: 9.24 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/reselect@4.1.8: 8.85 KB (0.144%)
|
||||
reselect: 8.85 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/xml@1.0.1: 7.39 KB (0.121%)
|
||||
xml: 7.39 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-copy-to-clipboard@5.1.0_react@17.0.2: 7.33 KB (0.120%)
|
||||
react-copy-to-clipboard: 7.33 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/traverse@0.6.7: 7.15 KB (0.117%)
|
||||
traverse: 7.15 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react@17.0.2: 6.48 KB (0.106%)
|
||||
react: 6.48 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-json-pointer@0.80.0: 6.39 KB (0.104%)
|
||||
@swagger-api/apidom-json-pointer: 6.39 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/randexp@0.5.3: 6.15 KB (0.100%)
|
||||
randexp: 6.15 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/react-immutable-pure-component@2.2.2_immutable@3.8.2_react-dom@17.0.2_react@17.0.2: 6.01 KB (0.0980%)
|
||||
react-immutable-pure-component: 6.01 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/redux-immutable@4.0.0_immutable@3.8.2: 5.43 KB (0.0886%)
|
||||
redux-immutable: 5.43 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/process@0.11.10: 5.29 KB (0.0863%)
|
||||
process: 5.29 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@babel+runtime@7.23.2: 5.1 KB (0.0833%)
|
||||
@babel/runtime: 5.1 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/cookie@0.5.0: 5.1 KB (0.0832%)
|
||||
cookie: 5.1 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/scheduler@0.20.2: 4.91 KB (0.0801%)
|
||||
scheduler: 4.91 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/drange@1.1.1: 4.8 KB (0.0783%)
|
||||
drange: 4.8 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/lowlight@1.20.0: 4.42 KB (0.0721%)
|
||||
lowlight: 4.42 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/deep-extend@0.6.0: 4.19 KB (0.0684%)
|
||||
deep-extend: 4.19 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/deepmerge@4.3.1: 3.95 KB (0.0645%)
|
||||
deepmerge: 3.95 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/base64-js@1.5.1: 3.84 KB (0.0626%)
|
||||
base64-js: 3.84 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/stream-browserify@3.0.0: 3.76 KB (0.0613%)
|
||||
stream-browserify: 3.76 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@swagger-api+apidom-error@0.80.0: 3.49 KB (0.0570%)
|
||||
@swagger-api/apidom-error: 3.49 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/side-channel@1.0.4: 3.31 KB (0.0540%)
|
||||
side-channel: 3.31 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/copy-to-clipboard@3.3.3: 3.29 KB (0.0536%)
|
||||
copy-to-clipboard: 3.29 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/format@0.2.2: 3.26 KB (0.0531%)
|
||||
format: 3.26 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/stampit@4.3.2: 3.16 KB (0.0515%)
|
||||
stampit: 3.16 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/css.escape@1.5.1: 3.08 KB (0.0502%)
|
||||
css.escape: 3.08 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/serialize-error@8.1.0: 2.93 KB (0.0477%)
|
||||
serialize-error: 2.93 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/define-data-property@1.1.1: 2.77 KB (0.0452%)
|
||||
define-data-property: 2.77 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/hoist-non-react-statics@3.3.2: 2.68 KB (0.0437%)
|
||||
hoist-non-react-statics: 2.68 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/prop-types@15.8.1: 2.6 KB (0.0424%)
|
||||
prop-types: 2.6 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/use-sync-external-store@1.2.0_react@17.0.2: 2.59 KB (0.0423%)
|
||||
use-sync-external-store: 2.59 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/querystringify@2.2.0: 2.5 KB (0.0408%)
|
||||
querystringify: 2.5 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/xml-but-prettier@1.0.1: 2.17 KB (0.0353%)
|
||||
xml-but-prettier: 2.17 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/has-symbols@1.0.3: 2.13 KB (0.0347%)
|
||||
has-symbols: 2.13 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/function-bind@1.1.2: 2.12 KB (0.0345%)
|
||||
function-bind: 2.12 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/ieee754@1.2.1: 2.1 KB (0.0343%)
|
||||
ieee754: 2.1 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/object-assign@4.1.1: 2.06 KB (0.0336%)
|
||||
object-assign: 2.06 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/call-bind@1.0.5: 1.59 KB (0.0259%)
|
||||
call-bind: 1.59 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/util-deprecate@1.0.2: 1.58 KB (0.0257%)
|
||||
util-deprecate: 1.58 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@braintree+sanitize-url@6.0.4: 1.56 KB (0.0255%)
|
||||
@braintree/sanitize-url: 1.56 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/randombytes@2.1.0: 1.54 KB (0.0251%)
|
||||
randombytes: 1.54 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/js-file-download@0.4.12: 1.52 KB (0.0248%)
|
||||
js-file-download: 1.52 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/classnames@2.3.2: 1.38 KB (0.0226%)
|
||||
classnames: 1.38 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/repeat-string@1.6.1: 1.18 KB (0.0193%)
|
||||
repeat-string: 1.18 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/set-function-length@1.1.1: 1.14 KB (0.0186%)
|
||||
set-function-length: 1.14 KB (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/@babel+runtime-corejs3@7.23.2: 878 B (0.0140%)
|
||||
@babel/runtime-corejs3: 878 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/has-property-descriptors@1.0.1: 817 B (0.0130%)
|
||||
has-property-descriptors: 817 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/toggle-selection@1.0.6: 780 B (0.0124%)
|
||||
toggle-selection: 780 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/is-plain-object@5.0.0: 758 B (0.0121%)
|
||||
is-plain-object: 758 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/inherits@2.0.4: 753 B (0.0120%)
|
||||
inherits: 753 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/requires-port@1.0.0: 753 B (0.0120%)
|
||||
requires-port: 753 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/fault@1.0.4: 691 B (0.0110%)
|
||||
fault: 691 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/gopd@1.0.1: 263 B (0.00419%)
|
||||
gopd: 263 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/hasown@2.0.0: 234 B (0.00373%)
|
||||
hasown: 234 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
.pnpm/has-proto@1.0.1: 197 B (0.00314%)
|
||||
has-proto: 197 B (100%)
|
||||
<self>: 0 B (0.00%)
|
||||
<self>: 2.74 MB (45.8%)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
readable-stream: 96.66 KB (7.62%)
|
||||
buffer: 56.99 KB (4.49%)
|
||||
core-js-pure: 25.99 KB (2.05%)
|
||||
sha.js: 18.57 KB (1.46%)
|
||||
events: 14.54 KB (1.15%)
|
||||
string_decoder: 9.24 KB (0.728%)
|
||||
xml: 7.39 KB (0.582%)
|
||||
process: 5.29 KB (0.417%)
|
||||
deep-extend: 4.19 KB (0.330%)
|
||||
stream-browserify: 3.76 KB (0.296%)
|
||||
safe-buffer: 1.63 KB (0.129%)
|
||||
util-deprecate: 1.58 KB (0.124%)
|
||||
randombytes: 1.54 KB (0.121%)
|
||||
inherits: 753 B (0.0579%)
|
||||
@babel/runtime-corejs3: 71 B (0.00546%)
|
||||
<self>: 1020.83 KB (80.4%)
|
|
@ -1,99 +0,0 @@
|
|||
ramda: 361.65 KB (6.14%)
|
||||
core-js-pure: 300.89 KB (5.11%)
|
||||
ramda-adjunct: 255.42 KB (4.34%)
|
||||
lodash: 253.56 KB (4.30%)
|
||||
@swagger-api/apidom-ns-openapi-3-0: 204.6 KB (3.47%)
|
||||
autolinker: 203.32 KB (3.45%)
|
||||
swagger-client: 159.41 KB (2.71%)
|
||||
is-plain-object: 758 B (0.464%)
|
||||
<self>: 158.67 KB (99.5%)
|
||||
@swagger-api/apidom-ns-openapi-3-1: 154.72 KB (2.63%)
|
||||
immutable: 139.01 KB (2.36%)
|
||||
remarkable: 125.56 KB (2.13%)
|
||||
react-dom: 119.19 KB (2.02%)
|
||||
highlight.js: 111.85 KB (1.90%)
|
||||
js-yaml: 105.01 KB (1.78%)
|
||||
readable-stream: 96.66 KB (1.64%)
|
||||
@swagger-api/apidom-reference: 84.72 KB (1.44%)
|
||||
dompurify: 61.27 KB (1.04%)
|
||||
@swagger-api/apidom-ns-json-schema-draft-4: 58.11 KB (0.987%)
|
||||
minim: 57.35 KB (0.974%)
|
||||
buffer: 56.99 KB (0.967%)
|
||||
@swagger-api/apidom-core: 47.72 KB (0.810%)
|
||||
@swagger-api/apidom-ast: 44.47 KB (0.755%)
|
||||
react-redux: 43.7 KB (0.742%)
|
||||
react-syntax-highlighter: 38.01 KB (0.645%)
|
||||
url: 37.24 KB (0.632%)
|
||||
punycode: 14.33 KB (38.5%)
|
||||
<self>: 22.92 KB (61.5%)
|
||||
fast-json-patch: 31.89 KB (0.541%)
|
||||
redux: 27.59 KB (0.468%)
|
||||
qs: 26.61 KB (0.452%)
|
||||
sha.js: 18.57 KB (0.315%)
|
||||
object-inspect: 18.15 KB (0.308%)
|
||||
url-parse: 16.23 KB (0.276%)
|
||||
events: 14.54 KB (0.247%)
|
||||
cross-fetch: 14.31 KB (0.243%)
|
||||
tslib: 14.21 KB (0.241%)
|
||||
@babel/runtime-corejs3: 13.24 KB (0.225%)
|
||||
get-intrinsic: 13.01 KB (0.221%)
|
||||
reselect: 12.51 KB (0.212%)
|
||||
zenscroll: 12.31 KB (0.209%)
|
||||
react-debounce-input: 11.95 KB (0.203%)
|
||||
react-immutable-proptypes: 11.82 KB (0.201%)
|
||||
ret: 10.82 KB (0.184%)
|
||||
lodash.debounce: 10.53 KB (0.179%)
|
||||
unraw: 10.27 KB (0.174%)
|
||||
string_decoder: 9.24 KB (0.157%)
|
||||
short-unique-id: 8.29 KB (0.141%)
|
||||
xml: 7.39 KB (0.125%)
|
||||
react-copy-to-clipboard: 7.33 KB (0.124%)
|
||||
traverse: 7.15 KB (0.121%)
|
||||
react: 6.48 KB (0.110%)
|
||||
@babel/runtime: 6.29 KB (0.107%)
|
||||
randexp: 6.15 KB (0.104%)
|
||||
react-immutable-pure-component: 6.01 KB (0.102%)
|
||||
redux-immutable: 5.43 KB (0.0922%)
|
||||
process: 5.29 KB (0.0898%)
|
||||
cookie: 5.1 KB (0.0866%)
|
||||
scheduler: 4.91 KB (0.0834%)
|
||||
drange: 4.8 KB (0.0815%)
|
||||
lowlight: 4.42 KB (0.0751%)
|
||||
deep-extend: 4.19 KB (0.0712%)
|
||||
deepmerge: 3.95 KB (0.0671%)
|
||||
base64-js: 3.84 KB (0.0652%)
|
||||
stream-browserify: 3.76 KB (0.0638%)
|
||||
@swagger-api/apidom-json-pointer: 3.68 KB (0.0625%)
|
||||
side-channel: 3.31 KB (0.0562%)
|
||||
copy-to-clipboard: 3.29 KB (0.0558%)
|
||||
format: 3.26 KB (0.0553%)
|
||||
stampit: 3.16 KB (0.0536%)
|
||||
css.escape: 3.08 KB (0.0523%)
|
||||
serialize-error: 2.93 KB (0.0497%)
|
||||
hoist-non-react-statics: 2.68 KB (0.0455%)
|
||||
prop-types: 2.6 KB (0.0441%)
|
||||
use-sync-external-store: 2.59 KB (0.0440%)
|
||||
querystringify: 2.5 KB (0.0425%)
|
||||
react-is: 2.48 KB (0.0422%)
|
||||
xml-but-prettier: 2.17 KB (0.0368%)
|
||||
has-symbols: 2.13 KB (0.0362%)
|
||||
ieee754: 2.1 KB (0.0357%)
|
||||
object-assign: 2.06 KB (0.0349%)
|
||||
call-bind: 1.68 KB (0.0285%)
|
||||
safe-buffer: 1.63 KB (0.0277%)
|
||||
util-deprecate: 1.58 KB (0.0268%)
|
||||
function-bind: 1.55 KB (0.0263%)
|
||||
randombytes: 1.54 KB (0.0261%)
|
||||
js-file-download: 1.52 KB (0.0258%)
|
||||
classnames: 1.38 KB (0.0235%)
|
||||
@braintree/sanitize-url: 1.38 KB (0.0234%)
|
||||
repeat-string: 1.18 KB (0.0201%)
|
||||
toggle-selection: 780 B (0.0129%)
|
||||
inherits: 753 B (0.0125%)
|
||||
requires-port: 753 B (0.0125%)
|
||||
fault: 691 B (0.0115%)
|
||||
formdata-node: 259 B (0.00429%)
|
||||
has-proto: 197 B (0.00327%)
|
||||
has: 129 B (0.00214%)
|
||||
object-inspect|.: 15 B (0.000249%)
|
||||
<self>: 2.26 MB (39.2%)
|
File diff suppressed because one or more lines are too long
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*!
|
||||
* @description Recursive object extending
|
||||
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
|
||||
* @license MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* cookie
|
||||
* Copyright(c) 2012-2014 Roman Shtylman
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||
* (c) 2017-2021 Joachim Wester
|
||||
* MIT license
|
||||
*/
|
||||
|
||||
/*!
|
||||
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
||||
*
|
||||
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* repeat-string <https://github.com/jonschlinkert/repeat-string>
|
||||
*
|
||||
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
/*! @license DOMPurify 3.0.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.3/LICENSE */
|
||||
|
||||
/*! https://mths.be/punycode v1.4.1 by @mathias */
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
|
||||
|
||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v0.20.2
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,35 +0,0 @@
|
|||
/*!
|
||||
* @description Recursive object extending
|
||||
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
|
||||
* @license MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/
|
||||
|
||||
/*!
|
||||
* @description Recursive object extending
|
||||
* @author Viacheslav Lotsmanov <lotsmanov89@gmail.com>
|
||||
* @license MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013-2018 Viacheslav Lotsmanov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* cookie
|
||||
* Copyright(c) 2012-2014 Roman Shtylman
|
||||
* Copyright(c) 2015 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/*!
|
||||
* https://github.com/Starcounter-Jack/JSON-Patch
|
||||
* (c) 2017-2021 Joachim Wester
|
||||
* MIT license
|
||||
*/
|
||||
|
||||
/*!
|
||||
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
||||
*
|
||||
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* repeat-string <https://github.com/jonschlinkert/repeat-string>
|
||||
*
|
||||
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
/*! @license DOMPurify 3.0.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.3/LICENSE */
|
||||
|
||||
/*! https://mths.be/punycode v1.4.1 by @mathias */
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
|
||||
|
||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v0.20.2
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
|
||||
|
||||
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
<. } .>
|
||||
|
||||
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||
Anonymously publish CAPTCHA performance statistics to help other webmasters. <a href="https://mcaptcha.org/blog/introducing-mcaptcha-net">Please see here for more info</a>.
|
||||
<input
|
||||
class="sitekey-form__input"
|
||||
type="checkbox"
|
||||
|
|
|
@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||
|
||||
|
||||
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||
Anonymously publish CAPTCHA performance statistics to help other webmasters. <a href="https://mcaptcha.org/blog/introducing-mcaptcha-net">Please see here for more info</a>.
|
||||
<input
|
||||
class="sitekey-form__input"
|
||||
type="checkbox"
|
||||
|
|
|
@ -6,51 +6,54 @@ SPDX-License-Identifier: MIT OR Apache-2.0
|
|||
|
||||
<. include!("../components/headers/widget-headers.html"); .>
|
||||
<body>
|
||||
<form class="widget__contaienr">
|
||||
<noscript>
|
||||
<div class="widget__noscript-container">
|
||||
<span class="widget__noscript-warning">
|
||||
Please enable JavaScript to receive mCaptcha challenge
|
||||
</span>
|
||||
<a class="widget__source-code" href="https://github.com/mCaptcha">
|
||||
Read our source code
|
||||
</a>
|
||||
</div>
|
||||
</noscript>
|
||||
<label class="widget__verification-container" for="widget__verification-checkbox">
|
||||
<input
|
||||
id="widget__verification-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 .>"
|
||||
class="widget__mcaptcha-logo-container"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
class="widget__mcaptcha-logo"
|
||||
src="<.= crate::FILES.get("./static/cache/img/icon-trans.png").unwrap().>"
|
||||
alt="mCaptcha logo"
|
||||
/>
|
||||
<p class="widget__mcaptcha-brand-name">mCaptcha</p>
|
||||
</a>
|
||||
<div class="widget__mcaptcha-info-container">
|
||||
<a class="widget__mcaptcha-info-link"
|
||||
target="_blank"
|
||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
|
||||
Privacy
|
||||
</a>
|
||||
<a class="widget__mcaptcha-info-link"
|
||||
target="_blank"
|
||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
|
||||
Terms
|
||||
<main class="widget__container">
|
||||
<form class="widget__inner-container">
|
||||
<noscript>
|
||||
<div class="widget__noscript-container">
|
||||
<span class="widget__noscript-warning">
|
||||
Please enable JavaScript to receive mCaptcha challenge
|
||||
</span>
|
||||
<a class="widget__source-code" href="https://github.com/mCaptcha">
|
||||
Read our source code
|
||||
</a>
|
||||
</div>
|
||||
</noscript>
|
||||
<label class="widget__verification-container" for="widget__verification-checkbox">
|
||||
<input
|
||||
id="widget__verification-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 .>"
|
||||
class="widget__mcaptcha-logo-container"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
class="widget__mcaptcha-logo"
|
||||
src="<.= crate::FILES.get("./static/cache/img/icon-trans.png").unwrap().>"
|
||||
alt="mCaptcha logo"
|
||||
/>
|
||||
<p class="widget__mcaptcha-brand-name">mCaptcha</p>
|
||||
</a>
|
||||
<div class="widget__mcaptcha-info-container">
|
||||
<a class="widget__mcaptcha-info-link"
|
||||
target="_blank"
|
||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
|
||||
Privacy
|
||||
</a>
|
||||
<a class="widget__mcaptcha-info-link"
|
||||
target="_blank"
|
||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
|
||||
Terms
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
<div class="progress__bar"><div class="progress__fill"></div></div>
|
||||
</main>
|
||||
<.include!("./footer.html"); .>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
import { Work, ServiceWorkerWork } from "./types";
|
||||
import { Work, ServiceWorkerMessage } from "./types";
|
||||
import fetchPoWConfig from "./fetchPoWConfig";
|
||||
import sendWork from "./sendWork";
|
||||
import sendToParent from "./sendToParent";
|
||||
|
@ -24,6 +24,9 @@ export const registerVerificationEventHandler = (): void => {
|
|||
};
|
||||
|
||||
export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
||||
const PROGRESS_FILL = <HTMLElement>document.querySelector(".progress__fill");
|
||||
let width = 0;
|
||||
|
||||
if (LOCK) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
|
@ -32,6 +35,8 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
|||
try {
|
||||
LOCK = true;
|
||||
if (CONST.btn().checked == false) {
|
||||
width = 0;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
CONST.messageText().before();
|
||||
LOCK = false;
|
||||
return;
|
||||
|
@ -43,32 +48,49 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
|||
CONST.messageText().during();
|
||||
// 1. get config
|
||||
const config = await fetchPoWConfig();
|
||||
const max_recorded_nonce = config.max_recorded_nonce;
|
||||
// 2. prove work
|
||||
worker.postMessage(config);
|
||||
|
||||
worker.onmessage = async (event: MessageEvent) => {
|
||||
const resp: ServiceWorkerWork = event.data;
|
||||
console.log(
|
||||
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.work.time}`
|
||||
);
|
||||
const resp: ServiceWorkerMessage = event.data;
|
||||
|
||||
const proof: Work = {
|
||||
key: CONST.sitekey(),
|
||||
string: config.string,
|
||||
nonce: resp.work.nonce,
|
||||
result: resp.work.result,
|
||||
time: Math.trunc(resp.work.time),
|
||||
worker_type: resp.work.worker_type,
|
||||
};
|
||||
if (resp.type === "work") {
|
||||
width = 80;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
console.log(
|
||||
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.value.work.time}`
|
||||
);
|
||||
|
||||
// 3. submit work
|
||||
const token = await sendWork(proof);
|
||||
// 4. send token
|
||||
sendToParent(token);
|
||||
// 5. mark checkbox checked
|
||||
CONST.btn().checked = true;
|
||||
CONST.messageText().after();
|
||||
LOCK = false;
|
||||
const proof: Work = {
|
||||
key: CONST.sitekey(),
|
||||
string: config.string,
|
||||
nonce: resp.value.work.nonce,
|
||||
result: resp.value.work.result,
|
||||
time: Math.trunc(resp.value.work.time),
|
||||
worker_type: resp.value.work.worker_type,
|
||||
};
|
||||
|
||||
width = 90;
|
||||
PROGRESS_FILL.style.width = `${width}%`;
|
||||
// 3. submit work
|
||||
const token = await sendWork(proof);
|
||||
// 4. send token
|
||||
sendToParent(token);
|
||||
// 5. mark checkbox checked
|
||||
CONST.btn().checked = true;
|
||||
width = 100;
|
||||
PROGRESS_FILL.style.width = `${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}%`;
|
||||
}
|
||||
console.log(`received nonce ${resp.nonce}`);
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
CONST.messageText().error();
|
||||
|
|
|
@ -7,106 +7,138 @@
|
|||
|
||||
@import "../reset";
|
||||
|
||||
.widget__contaienr {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.widget__container {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.widget__inner-container {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.widget__noscript-container {
|
||||
display: flex;
|
||||
font-size: 0.7rem;
|
||||
line-height: 20px;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
font-size: 0.7rem;
|
||||
line-height: 20px;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.widget__noscript-warning {
|
||||
display: block;
|
||||
margin: auto;
|
||||
flex: 2;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
margin: auto;
|
||||
flex: 2;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.widget__source-code {
|
||||
display: block;
|
||||
flex: 1;
|
||||
display: block;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.widget__verification-container {
|
||||
align-items: center;
|
||||
display: none;
|
||||
line-height: 30px;
|
||||
font-size: 1rem;
|
||||
align-items: center;
|
||||
display: none;
|
||||
line-height: 30px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
#widget__verification-text--during {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#widget__verification-text--after {
|
||||
display: none;
|
||||
color: green;
|
||||
display: none;
|
||||
color: green;
|
||||
}
|
||||
|
||||
#widget__verification-text--error {
|
||||
display: none;
|
||||
color: red;
|
||||
display: none;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--before {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--during {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--error {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget__verification-checkbox:checked ~ #widget__verification-text--after {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: auto;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: auto;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-brand-name {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-logo {
|
||||
display: block;
|
||||
width: 35px;
|
||||
margin: auto;
|
||||
display: block;
|
||||
width: 35px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-info-container {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.widget__mcaptcha-info-link {
|
||||
font-size: 0.5rem;
|
||||
margin: 2px;
|
||||
font-size: 0.5rem;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* progress bar courtesy of https://codepen.io/Bizzy-Coding/pen/poOymVJ?editors=1111 */
|
||||
.progress__bar {
|
||||
position: relative;
|
||||
height: 5px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.progress__fill {
|
||||
background: #65a2e0;
|
||||
border-radius: 15px;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
import { gen_pow } from "@mcaptcha/pow-wasm";
|
||||
import { stepped_gen_pow } from "@mcaptcha/pow-wasm";
|
||||
import * as p from "@mcaptcha/pow_sha256-polyfill";
|
||||
import { WasmWork, PoWConfig, SubmitWork } from "./types";
|
||||
|
||||
|
@ -12,19 +12,25 @@ import { WasmWork, PoWConfig, SubmitWork } from "./types";
|
|||
* @param {PoWConfig} config - the proof-of-work configuration using which
|
||||
* work needs to be computed
|
||||
* */
|
||||
const prove = async (config: PoWConfig): Promise<SubmitWork> => {
|
||||
const prove = async (
|
||||
config: PoWConfig,
|
||||
progress: (nonce: number) => void
|
||||
): Promise<SubmitWork> => {
|
||||
const WASM = "wasm";
|
||||
const JS = "js";
|
||||
const STEPS = 5000;
|
||||
if (WasmSupported) {
|
||||
let proof: WasmWork = null;
|
||||
let res: SubmitWork = null;
|
||||
let time: number = null;
|
||||
|
||||
const t0 = performance.now();
|
||||
const proofString = gen_pow(
|
||||
const proofString = stepped_gen_pow(
|
||||
config.salt,
|
||||
config.string,
|
||||
config.difficulty_factor
|
||||
config.difficulty_factor,
|
||||
STEPS,
|
||||
progress
|
||||
);
|
||||
const t1 = performance.now();
|
||||
time = t1 - t0;
|
||||
|
@ -47,10 +53,12 @@ const prove = async (config: PoWConfig): Promise<SubmitWork> => {
|
|||
|
||||
const t0 = performance.now();
|
||||
|
||||
proof = await p.generate_work(
|
||||
proof = await p.stepped_generate_work(
|
||||
config.salt,
|
||||
config.string,
|
||||
config.difficulty_factor
|
||||
config.difficulty_factor,
|
||||
STEPS,
|
||||
progress
|
||||
);
|
||||
const t1 = performance.now();
|
||||
time = t1 - t0;
|
||||
|
|
|
@ -6,17 +6,31 @@
|
|||
import log from "../logger";
|
||||
|
||||
import prove from "./prove";
|
||||
import { PoWConfig, ServiceWorkerWork } from "./types";
|
||||
import { PoWConfig, ServiceWorkerMessage, ServiceWorkerWork } from "./types";
|
||||
|
||||
log.log("worker registered");
|
||||
onmessage = async (e) => {
|
||||
console.debug("message received at worker");
|
||||
const config: PoWConfig = e.data;
|
||||
|
||||
const work = await prove(config);
|
||||
const res: ServiceWorkerWork = {
|
||||
const progressCallback = (nonce: number) => {
|
||||
const res: ServiceWorkerMessage = {
|
||||
type: "progress",
|
||||
nonce: nonce,
|
||||
};
|
||||
|
||||
postMessage(res);
|
||||
};
|
||||
|
||||
const work = await prove(config, progressCallback);
|
||||
const w: ServiceWorkerWork = {
|
||||
work,
|
||||
};
|
||||
|
||||
const res: ServiceWorkerMessage = {
|
||||
type: "work",
|
||||
value: w,
|
||||
};
|
||||
|
||||
postMessage(res);
|
||||
};
|
||||
|
|
|
@ -32,8 +32,13 @@ export type PoWConfig = {
|
|||
string: string;
|
||||
difficulty_factor: number;
|
||||
salt: string;
|
||||
max_recorded_nonce: number;
|
||||
};
|
||||
|
||||
export type Token = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export type ServiceWorkerMessage =
|
||||
| { type: "work"; value: ServiceWorkerWork }
|
||||
| { type: "progress"; nonce: number };
|
||||
|
|
Loading…
Add table
Reference in a new issue