mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2026-02-11 10:05:41 +00:00
Compare commits
72 Commits
wip-publis
...
fix-integr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8e6b81d6e | ||
|
|
71ef95e4c4 | ||
|
|
b2f373dffd | ||
|
|
c3ab7f8b0e | ||
|
|
c4a286454b | ||
|
|
4a25f7f3a6 | ||
|
|
32762cf6b5 | ||
|
|
43024f1940 | ||
|
|
1e0aedad61 | ||
|
|
1b05cdc391 | ||
|
|
3e5936cb83 | ||
|
|
2ca49cffe4 | ||
|
|
468752f691 | ||
|
|
22edb04ce2 | ||
|
|
6834e555d8 | ||
|
|
3c6b13f947 | ||
|
|
99db889867 | ||
|
|
56dba7b77f | ||
|
|
1c4ee5b622 | ||
|
|
d8a145cf87 | ||
|
|
8ca48d85ff | ||
|
|
2b82af9a0c | ||
|
|
2cf5e48d8e | ||
|
|
679a35216c | ||
|
|
68b59ade8c | ||
|
|
8af09939ff | ||
|
|
dc380adfcf | ||
|
|
f8e3b720de | ||
|
|
5ae3b1f26e | ||
|
|
2a1bda653d | ||
|
|
b0db04f26a | ||
|
|
746748f98c | ||
|
|
003ed983cb | ||
|
|
78de0b266f | ||
|
|
6ede578ad5 | ||
|
|
efed5f5f93 | ||
|
|
4a6850631a | ||
|
|
0adbb0aa2f | ||
|
|
8f3faaa279 | ||
|
|
5324969bd2 | ||
|
|
43dab030df | ||
|
|
9cc667851c | ||
|
|
9fc7c31083 | ||
|
|
90e60b0486 | ||
|
|
58f93cb602 | ||
|
|
fae50b19f8 | ||
|
|
e890ba0f57 | ||
|
|
744d94cf8d | ||
|
|
31d12206aa | ||
|
|
7764eda05d | ||
|
|
f78669955c | ||
|
|
cadc15a7a1 | ||
|
|
c1f6ce3ae2 | ||
|
|
864549cb4c | ||
|
|
5713d4b1ae | ||
|
|
ac502b7c08 | ||
|
|
8dc690ca01 | ||
|
|
a4f9c92b32 | ||
|
|
8826f6df8f | ||
|
|
af35fdb48e | ||
|
|
9fd8ffd666 | ||
|
|
521abd82d6 | ||
|
|
021f2fe5b4 | ||
|
|
b3e0ff6769 | ||
|
|
97abca2520 | ||
|
|
96a6c98c10 | ||
|
|
9d285573d7 | ||
|
|
d506d291c3 | ||
|
|
223e8fb8c2 | ||
|
|
2abf57d16b | ||
|
|
b3ee57d042 | ||
|
|
8c65edd257 |
2
.github/workflows/clippy-fmt.yml
vendored
2
.github/workflows/clippy-fmt.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "14.x"
|
node-version: "18.0.0"
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: make frontend
|
run: make frontend
|
||||||
|
|||||||
6
.github/workflows/coverage.yml
vendored
6
.github/workflows/coverage.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
maria:
|
maria:
|
||||||
image: mariadb
|
image: mariadb:10
|
||||||
env:
|
env:
|
||||||
MARIADB_USER: "maria"
|
MARIADB_USER: "maria"
|
||||||
MARIADB_PASSWORD: "password"
|
MARIADB_PASSWORD: "password"
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "16.x"
|
node-version: "18.0.0"
|
||||||
|
|
||||||
- name: Install ${{ matrix.version }}
|
- name: Install ${{ matrix.version }}
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
run: make frontend
|
run: make frontend
|
||||||
|
|
||||||
- name: Run the frontend tests
|
- name: Run the frontend tests
|
||||||
run: make frontend-test
|
run: make test.frontend
|
||||||
|
|
||||||
- name: Run migrations
|
- name: Run migrations
|
||||||
run: make migrate
|
run: make migrate
|
||||||
|
|||||||
12
.github/workflows/linux.yml
vendored
12
.github/workflows/linux.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
- 10025:1025
|
- 10025:1025
|
||||||
|
|
||||||
maria:
|
maria:
|
||||||
image: mariadb
|
image: mariadb:10
|
||||||
env:
|
env:
|
||||||
MARIADB_USER: "maria"
|
MARIADB_USER: "maria"
|
||||||
MARIADB_PASSWORD: "password"
|
MARIADB_PASSWORD: "password"
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: configure GPG key
|
- name: configure GPG key
|
||||||
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'realaravinth/dumbserve'
|
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
|
||||||
run: echo -n "$RELEASE_BOT_GPG_SIGNING_KEY" | gpg --batch --import --pinentry-mode loopback
|
run: echo -n "$RELEASE_BOT_GPG_SIGNING_KEY" | gpg --batch --import --pinentry-mode loopback
|
||||||
env:
|
env:
|
||||||
RELEASE_BOT_GPG_SIGNING_KEY: ${{ secrets.RELEASE_BOT_GPG_SIGNING_KEY }}
|
RELEASE_BOT_GPG_SIGNING_KEY: ${{ secrets.RELEASE_BOT_GPG_SIGNING_KEY }}
|
||||||
@@ -95,7 +95,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "16.x"
|
node-version: "18.0.0"
|
||||||
|
|
||||||
- name: Install ${{ matrix.version }}
|
- name: Install ${{ matrix.version }}
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -104,6 +104,9 @@ jobs:
|
|||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
- name: install nightwatch dep
|
||||||
|
run: sudo apt-get install xvfb
|
||||||
|
|
||||||
- name: Run migrations
|
- name: Run migrations
|
||||||
run: make migrate
|
run: make migrate
|
||||||
env:
|
env:
|
||||||
@@ -128,6 +131,9 @@ jobs:
|
|||||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||||
|
|
||||||
|
- name: run integration tests
|
||||||
|
run: make test.integration
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- 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
|
uses: docker/login-action@v1
|
||||||
|
|||||||
1339
Cargo.lock
generated
1339
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -59,8 +59,8 @@ log = "0.4"
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
|
||||||
|
|
||||||
#libmcaptcha = { version = "0.2.2", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"], tag ="0.2.2" }
|
libmcaptcha = { version = "0.2.3", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"], tag ="0.2.3" }
|
||||||
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
#libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
||||||
#libmcaptcha = { path = "../libmcaptcha", features = ["full"]}
|
#libmcaptcha = { path = "../libmcaptcha", features = ["full"]}
|
||||||
|
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
@@ -78,7 +78,8 @@ lettre = { version = "0.10.0-rc.3", features = [
|
|||||||
"smtp-transport"
|
"smtp-transport"
|
||||||
]}
|
]}
|
||||||
|
|
||||||
openssl = { version = "0.10.29", features = ["vendored"] }
|
openssl = { version = "0.10.48", features = ["vendored"] }
|
||||||
|
uuid = { version = "1.4.0", features = ["v4", "serde"] }
|
||||||
|
|
||||||
|
|
||||||
[dependencies.db-core]
|
[dependencies.db-core]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:16.0.0 as frontend
|
FROM node:18.0.0 as frontend
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -38,6 +38,7 @@ COPY --from=cacher /src/target target
|
|||||||
#COPY --from=cacher /src/db/db-migrations/target /src/db/db-migrations/target
|
#COPY --from=cacher /src/db/db-migrations/target /src/db/db-migrations/target
|
||||||
#COPY --from=cacher /src/utils/cache-bust/target /src/utils/cache-bust/target
|
#COPY --from=cacher /src/utils/cache-bust/target /src/utils/cache-bust/target
|
||||||
COPY --from=frontend /src/static/cache/bundle/ /src/static/cache/bundle/
|
COPY --from=frontend /src/static/cache/bundle/ /src/static/cache/bundle/
|
||||||
|
COPY --from=frontend /src/docs/openapi/dist/ /src/docs/openapi/dist/
|
||||||
RUN cargo --version
|
RUN cargo --version
|
||||||
RUN make cache-bust
|
RUN make cache-bust
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|||||||
132
Makefile
132
Makefile
@@ -2,6 +2,39 @@ BUNDLE = static/cache/bundle
|
|||||||
OPENAPI = docs/openapi
|
OPENAPI = docs/openapi
|
||||||
CLEAN_UP = $(BUNDLE) src/cache_buster_data.json assets
|
CLEAN_UP = $(BUNDLE) src/cache_buster_data.json assets
|
||||||
|
|
||||||
|
define deploy_dependencies ## deploy dependencies
|
||||||
|
@-docker create --name ${db} \
|
||||||
|
-e POSTGRES_PASSWORD=password \
|
||||||
|
-p 5432:5432 \
|
||||||
|
postgres
|
||||||
|
@-docker create \
|
||||||
|
-p 3306:3306 \
|
||||||
|
--name ${mdb} \
|
||||||
|
--env MARIADB_USER=maria \
|
||||||
|
--env MARIADB_PASSWORD=password \
|
||||||
|
--env MARIADB_ROOT_PASSWORD=password \
|
||||||
|
--env MARIADB_DATABASE=maria \
|
||||||
|
mariadb:latest
|
||||||
|
@-docker create \
|
||||||
|
-p 6379:6379 \
|
||||||
|
--name mcaptcha-cache \
|
||||||
|
mcaptcha/cache:latest
|
||||||
|
docker start ${db}
|
||||||
|
docker start ${mdb}
|
||||||
|
docker start mcaptcha-cache
|
||||||
|
endef
|
||||||
|
|
||||||
|
define run_migrations ## run database migrations
|
||||||
|
cd db/db-migrations/ && cargo run
|
||||||
|
endef
|
||||||
|
|
||||||
|
define run_dev_migrations ## run database migrations
|
||||||
|
cd db/db-sqlx-maria/ && \
|
||||||
|
DATABASE_URL=${MARIA_DATABASE_URL} sqlx migrate run
|
||||||
|
cd db/db-sqlx-postgres/ && \
|
||||||
|
DATABASE_URL=${POSTGRES_DATABASE_URL} sqlx migrate run
|
||||||
|
endef
|
||||||
|
|
||||||
define frontend_env ## install frontend deps
|
define frontend_env ## install frontend deps
|
||||||
yarn install
|
yarn install
|
||||||
cd docs/openapi && yarn install
|
cd docs/openapi && yarn install
|
||||||
@@ -11,6 +44,30 @@ define cache_bust ## run cache_busting program
|
|||||||
cd utils/cache-bust && cargo run
|
cd utils/cache-bust && cargo run
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
|
||||||
|
define test_frontend ## run frontend tests
|
||||||
|
cd $(OPENAPI)&& yarn test
|
||||||
|
yarn test
|
||||||
|
endef
|
||||||
|
|
||||||
|
define test_db_sqlx_postgres
|
||||||
|
cd db/db-sqlx-postgres &&\
|
||||||
|
DATABASE_URL=${POSTGRES_DATABASE_URL}\
|
||||||
|
cargo test --no-fail-fast
|
||||||
|
endef
|
||||||
|
|
||||||
|
define test_db_sqlx_maria
|
||||||
|
cd db/db-sqlx-maria &&\
|
||||||
|
DATABASE_URL=${MARIA_DATABASE_URL}\
|
||||||
|
cargo test --no-fail-fast
|
||||||
|
endef
|
||||||
|
|
||||||
|
define test_core
|
||||||
|
cargo test --no-fail-fast
|
||||||
|
endef
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
default: frontend ## Build app in debug mode
|
default: frontend ## Build app in debug mode
|
||||||
$(call cache_bust)
|
$(call cache_bust)
|
||||||
cargo build
|
cargo build
|
||||||
@@ -35,10 +92,6 @@ clean: ## Delete build artifacts
|
|||||||
@yarn cache clean
|
@yarn cache clean
|
||||||
@-rm $(CLEAN_UP)
|
@-rm $(CLEAN_UP)
|
||||||
|
|
||||||
coverage: migrate ## Generate code coverage report in HTML format
|
|
||||||
$(call cache_bust)
|
|
||||||
cargo tarpaulin -t 1200 --out Html
|
|
||||||
|
|
||||||
doc: ## Generate documentation
|
doc: ## Generate documentation
|
||||||
#yarn doc
|
#yarn doc
|
||||||
cargo doc --no-deps --workspace --all-features
|
cargo doc --no-deps --workspace --all-features
|
||||||
@@ -54,6 +107,19 @@ env: ## Setup development environtment
|
|||||||
cargo fetch
|
cargo fetch
|
||||||
$(call frontend_env)
|
$(call frontend_env)
|
||||||
|
|
||||||
|
env.db: ## Deploy dependencies
|
||||||
|
$(call deploy_dependencies)
|
||||||
|
sleep 5
|
||||||
|
$(call run_migrations)
|
||||||
|
|
||||||
|
env.db.recreate: ## Deploy dependencies from scratch
|
||||||
|
@-docker rm -f ${db}
|
||||||
|
@-docker rm -f ${mdb}
|
||||||
|
@-docker rm -f mcaptcha-cache
|
||||||
|
$(call deploy_dependencies)
|
||||||
|
sleep 5
|
||||||
|
$(call run_migrations)
|
||||||
|
|
||||||
frontend-env: ## Install frontend deps
|
frontend-env: ## Install frontend deps
|
||||||
$(call frontend_env)
|
$(call frontend_env)
|
||||||
|
|
||||||
@@ -76,10 +142,6 @@ frontend: ## Build frontend
|
|||||||
@./scripts/librejs.sh
|
@./scripts/librejs.sh
|
||||||
@./scripts/cachebust.sh
|
@./scripts/cachebust.sh
|
||||||
|
|
||||||
frontend-test: ## Run frontend tests
|
|
||||||
cd $(OPENAPI)&& yarn test
|
|
||||||
yarn test
|
|
||||||
|
|
||||||
lint: ## Lint codebase
|
lint: ## Lint codebase
|
||||||
cargo fmt -v --all -- --emit files
|
cargo fmt -v --all -- --emit files
|
||||||
cargo clippy --workspace --tests --all-features
|
cargo clippy --workspace --tests --all-features
|
||||||
@@ -87,7 +149,10 @@ lint: ## Lint codebase
|
|||||||
cd $(OPENAPI)&& yarn test
|
cd $(OPENAPI)&& yarn test
|
||||||
|
|
||||||
migrate: ## Run database migrations
|
migrate: ## Run database migrations
|
||||||
cd db/db-migrations/ && cargo run
|
$(call run_migrations)
|
||||||
|
|
||||||
|
migrate.dev: ## Run database migrations during development
|
||||||
|
$(call run_dev_migrations)
|
||||||
|
|
||||||
release: frontend ## Build app with release optimizations
|
release: frontend ## Build app with release optimizations
|
||||||
$(call cache_bust)
|
$(call cache_bust)
|
||||||
@@ -98,34 +163,49 @@ run: frontend ## Run app in debug mode
|
|||||||
cargo run
|
cargo run
|
||||||
|
|
||||||
|
|
||||||
sqlx-offline-data: ## prepare sqlx offline data
|
db.sqlx.offline: ## prepare sqlx offline data
|
||||||
cd db/db-sqlx-postgres && cargo sqlx prepare \
|
cd db/db-sqlx-postgres && cargo sqlx prepare \
|
||||||
--database-url=${POSTGRES_DATABASE_URL} -- \
|
--database-url=${POSTGRES_DATABASE_URL} -- \
|
||||||
--all-features
|
--all-features
|
||||||
cd db/db-sqlx-maria && cargo sqlx prepare \
|
cd db/db-sqlx-maria && cargo sqlx prepare \
|
||||||
--database-url=${MARIA_DATABASE_URL} -- \
|
--database-url=${MARIA_DATABASE_URL} -- \
|
||||||
--all-features
|
--all-features
|
||||||
# cd db/db-sqlx-sqlite/ \
|
|
||||||
# && DATABASE_URL=${SQLITE_DATABASE_URL} cargo sqlx prepare
|
|
||||||
|
|
||||||
test-db: ## run tests on database
|
test: frontend ## Run all available tests
|
||||||
cd db/db-sqlx-postgres &&\
|
$(call test_frontend)
|
||||||
DATABASE_URL=${POSTGRES_DATABASE_URL}\
|
|
||||||
cargo test --no-fail-fast
|
|
||||||
test: frontend-test frontend ## Run all available tests
|
|
||||||
$(call cache_bust)
|
$(call cache_bust)
|
||||||
cd db/db-sqlx-postgres &&\
|
$(call test_db_sqlx_postgres)
|
||||||
DATABASE_URL=${POSTGRES_DATABASE_URL}\
|
$(call test_db_sqlx_maria)
|
||||||
cargo test --no-fail-fast
|
$(call test_core)
|
||||||
cd db/db-sqlx-maria &&\
|
|
||||||
DATABASE_URL=${MARIA_DATABASE_URL}\
|
|
||||||
cargo test --no-fail-fast
|
|
||||||
cargo test --no-fail-fast
|
|
||||||
# ./scripts/tests.sh
|
# ./scripts/tests.sh
|
||||||
|
|
||||||
xml-test-coverage: migrate ## Generate code coverage report in XML format
|
test.cov.html: migrate ## Generate code coverage report in HTML format
|
||||||
|
$(call cache_bust)
|
||||||
|
cargo tarpaulin -t 1200 --out Html
|
||||||
|
|
||||||
|
test.cov.xml: migrate ## Generate code coverage report in XML format
|
||||||
$(call cache_bust)
|
$(call cache_bust)
|
||||||
cargo tarpaulin -t 1200 --out Xml
|
cargo tarpaulin -t 1200 --out Xml
|
||||||
|
|
||||||
|
|
||||||
|
test.core: ## Run all core tests
|
||||||
|
$(call test_core)
|
||||||
|
|
||||||
|
test.db: ## Run all database driver tests
|
||||||
|
$(call test_db_sqlx_postgres)
|
||||||
|
$(call test_db_sqlx_maria)
|
||||||
|
|
||||||
|
test.db.pg: ## Run Postgres database driver tests
|
||||||
|
$(call test_db_sqlx_postgres)
|
||||||
|
|
||||||
|
test.db.maria: ## Run Maria database driver tests
|
||||||
|
$(call test_db_sqlx_maria)
|
||||||
|
|
||||||
|
test.frontend: ## Run frontend tests
|
||||||
|
$(call test_frontend)
|
||||||
|
|
||||||
|
test.integration: ## run integration tests with nightwatch.js
|
||||||
|
./scripts/integration.sh
|
||||||
|
|
||||||
help: ## Prints help for targets with comments
|
help: ## Prints help for targets with comments
|
||||||
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
@cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-].+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|||||||
63
README.md
63
README.md
@@ -32,18 +32,18 @@ yourself!](https://demo.mcaptcha.org/widget/?sitekey=pHy0AktWyOKuxZDzFfoaewncWec
|
|||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
mCaptcha uses SHA256 based proof-of-work(PoW) to rate limit users.
|
mCaptcha uses SHA256 based proof-of-work (PoW) to rate limit users.
|
||||||
|
|
||||||
When a user wants to do something on an mCaptcha-protected website,
|
When a user wants to do something on a mCaptcha-protected website,
|
||||||
|
|
||||||
1. they will have to generate proof-of-work(a bunch of math that will takes
|
1. they will have to generate proof-of-work (a bunch of math that will takes
|
||||||
time to compute) and submit it to mCaptcha.
|
time to compute) and submit it to mCaptcha.
|
||||||
|
|
||||||
2. We'll validate the proof:
|
2. We'll validate the proof:
|
||||||
|
|
||||||
- **if validation is unsuccessful**, they will be prevented from
|
- **if validation is unsuccessful**, they will be prevented from
|
||||||
accessing their target website
|
accessing their target website
|
||||||
- **if validation is successful**, read on,
|
- **if validation is successful**, read on,
|
||||||
|
|
||||||
3. They will be issued a token that they should submit along
|
3. They will be issued a token that they should submit along
|
||||||
with their request/form submission to the target website.
|
with their request/form submission to the target website.
|
||||||
@@ -54,8 +54,8 @@ When a user wants to do something on an mCaptcha-protected website,
|
|||||||
The whole process is automated from the user's POV. All they have to do
|
The whole process is automated from the user's POV. All they have to do
|
||||||
is click on a button to initiate the process.
|
is click on a button to initiate the process.
|
||||||
|
|
||||||
mCaptcha makes interacting with websites (computationally)expensive for
|
mCaptcha makes interacting with websites (computationally) expensive for
|
||||||
the user. A well-behaving user will experience a slight delay(no delay
|
the user. A well-behaving user will experience a slight delay (no delay
|
||||||
when under moderate load to 2s when under attack; PoW difficulty is
|
when under moderate load to 2s when under attack; PoW difficulty is
|
||||||
variable) but if someone wants to hammer your site, they will have to do
|
variable) but if someone wants to hammer your site, they will have to do
|
||||||
more work to send requests than your server will have to do to respond
|
more work to send requests than your server will have to do to respond
|
||||||
@@ -63,14 +63,14 @@ to their request.
|
|||||||
|
|
||||||
## Why use mCaptcha?
|
## Why use mCaptcha?
|
||||||
|
|
||||||
- [x] **Free software, privacy focused**
|
- [x] **Free software, privacy focused**
|
||||||
- [x] **Seamless UX** - No more annoying CAPTCHAs!
|
- [x] **Seamless UX** - No more annoying CAPTCHAs!
|
||||||
- [x] **No tracking:** Our CAPTCHA routes are cookie free!
|
- [x] **No tracking:** Our CAPTCHA routes are cookie free!
|
||||||
- [x] **IP address independent:** your users are behind a NAT? We got you covered!
|
- [x] **IP address independent:** your users are behind a NAT? We got you covered!
|
||||||
- [x] **Resistant to replay attacks:** proof-of-work configurations have
|
- [x] **Resistant to replay attacks:** proof-of-work configurations have
|
||||||
short lifetimes(30s) and can be used only once. If a user submits a
|
short lifetimes (30s) and can be used only once. If a user submits a
|
||||||
PoW to an already used configuration or an expired one, their proof
|
PoW to an already used configuration or an expired one, their proof
|
||||||
will be rejected.
|
will be rejected.
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
@@ -87,15 +87,15 @@ monitor console and network activity.
|
|||||||
|
|
||||||
### Demo servers are available at:
|
### Demo servers are available at:
|
||||||
|
|
||||||
- https://demo.mcaptcha.org/
|
- https://demo.mcaptcha.org/
|
||||||
- https://demo2.mcaptcha.org/ (runs on a Raspberry Pi!)
|
- https://demo2.mcaptcha.org/ (runs on a Raspberry Pi!)
|
||||||
|
|
||||||
> Core functionality is working but it's still very much
|
> Core functionality is working but it's still very much
|
||||||
> work-in-progress. Since we don't have a stable release yet, hosted
|
> work-in-progress. Since we don't have a stable release yet, hosted
|
||||||
> demo servers might be a few versions behind `master`. Please check footer for
|
> demo servers might be a few versions behind `master`. Please check footer for
|
||||||
> build commit.
|
> build commit.
|
||||||
|
|
||||||
Feel free to provide bogus information while signing up(project under
|
Feel free to provide bogus information while signing up (project under
|
||||||
development, database frequently wiped).
|
development, database frequently wiped).
|
||||||
|
|
||||||
### Self-hosted:
|
### Self-hosted:
|
||||||
@@ -109,13 +109,12 @@ docker-compose up -d
|
|||||||
|
|
||||||
After the containers are up, visit [http://localhost:7000](http://localhost:7000) and login with the default credentials:
|
After the containers are up, visit [http://localhost:7000](http://localhost:7000) and login with the default credentials:
|
||||||
|
|
||||||
- username: aaronsw
|
- username: aaronsw
|
||||||
- password: password
|
- password: password
|
||||||
|
|
||||||
|
|
||||||
It takes a while to build the image so please be patient :)
|
It takes a while to build the image so please be patient :)
|
||||||
|
|
||||||
See [DEPLOYMENT.md](./docs/DEPLOYMENT.md) detailed alternate deployment
|
See [DEPLOYMENT.md](./docs/DEPLOYMENT.md) for detailed alternate deployment
|
||||||
methods.
|
methods.
|
||||||
|
|
||||||
## Development:
|
## Development:
|
||||||
@@ -129,3 +128,21 @@ See [DEPLOYMENT.md](./docs/DEPLOYMENT.md)
|
|||||||
## Configuration:
|
## Configuration:
|
||||||
|
|
||||||
See [CONFIGURATION.md](./docs/CONFIGURATION.md)
|
See [CONFIGURATION.md](./docs/CONFIGURATION.md)
|
||||||
|
|
||||||
|
## Funding
|
||||||
|
|
||||||
|
### NLnet
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img
|
||||||
|
height="150px"
|
||||||
|
alt="NLnet NGIZero logo"
|
||||||
|
src="./docs/third-party/NGIZero-green.hex.svg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
2023 development is funded through the [NGI0 Entrust
|
||||||
|
Fund](https://nlnet.nl/entrust), via [NLnet](https://nlnet.nl/). Please
|
||||||
|
see [here](https://nlnet.nl/project/mCaptcha/) for more details.
|
||||||
|
|||||||
2
build.rs
2
build.rs
@@ -21,7 +21,7 @@ use sqlx::types::time::OffsetDateTime;
|
|||||||
fn main() {
|
fn main() {
|
||||||
// note: add error checking yourself.
|
// note: add error checking yourself.
|
||||||
let output = Command::new("git")
|
let output = Command::new("git")
|
||||||
.args(&["rev-parse", "HEAD"])
|
.args(["rev-parse", "HEAD"])
|
||||||
.output()
|
.output()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let git_hash = String::from_utf8(output.stdout).unwrap();
|
let git_hash = String::from_utf8(output.stdout).unwrap();
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ async-trait = "0.1.51"
|
|||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
serde = { version = "1", features = ["derive"]}
|
serde = { version = "1", features = ["derive"]}
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
#libmcaptcha = { version = "0.2.2", git = "https://github.com/mCaptcha/libmcaptcha", features = ["minimal"], default-features = false, tag = "0.2.2"}
|
libmcaptcha = { version = "0.2.3", git = "https://github.com/mCaptcha/libmcaptcha", features = ["minimal"], default-features = false, tag = "0.2.3"}
|
||||||
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
#libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
@@ -250,6 +250,81 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
|||||||
|
|
||||||
/// fetch PoWConfig confirms
|
/// fetch PoWConfig confirms
|
||||||
async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult<Vec<i64>>;
|
async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult<Vec<i64>>;
|
||||||
|
|
||||||
|
/// record PoW timing
|
||||||
|
async fn analysis_save(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
d: &CreatePerformanceAnalytics,
|
||||||
|
) -> DBResult<()>;
|
||||||
|
|
||||||
|
/// fetch PoW analytics
|
||||||
|
async fn analytics_fetch(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<PerformanceAnalytics>>;
|
||||||
|
|
||||||
|
/// Create psuedo ID against campaign ID to publish analytics
|
||||||
|
async fn analytics_create_psuedo_id_if_not_exists(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
) -> DBResult<()>;
|
||||||
|
|
||||||
|
/// Get psuedo ID from campaign ID
|
||||||
|
async fn analytics_get_psuedo_id_from_capmaign_id(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
) -> DBResult<String>;
|
||||||
|
|
||||||
|
/// Get campaign ID from psuedo ID
|
||||||
|
async fn analytics_get_capmaign_id_from_psuedo_id(
|
||||||
|
&self,
|
||||||
|
psuedo_id: &str,
|
||||||
|
) -> DBResult<String>;
|
||||||
|
|
||||||
|
/// Delete all records for campaign
|
||||||
|
async fn analytics_delete_all_records_for_campaign(
|
||||||
|
&self,
|
||||||
|
campaign_id: &str,
|
||||||
|
) -> DBResult<()>;
|
||||||
|
|
||||||
|
/// Get publishing status of pow analytics for captcha ID/ campaign ID
|
||||||
|
async fn analytics_captcha_is_published(&self, campaign_id: &str) -> DBResult<bool> {
|
||||||
|
match self
|
||||||
|
.analytics_get_psuedo_id_from_capmaign_id(campaign_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(errors::DBError::CaptchaNotFound) => Ok(false),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||||
|
/// Log Proof-of-Work CAPTCHA performance analytics
|
||||||
|
pub struct CreatePerformanceAnalytics {
|
||||||
|
/// time taken to generate proof
|
||||||
|
pub time: u32,
|
||||||
|
/// difficulty factor for which the proof was generated
|
||||||
|
pub difficulty_factor: u32,
|
||||||
|
/// worker/client type: wasm, javascript, python, etc.
|
||||||
|
pub worker_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||||
|
/// Proof-of-Work CAPTCHA performance analytics
|
||||||
|
pub struct PerformanceAnalytics {
|
||||||
|
/// log ID
|
||||||
|
pub id: usize,
|
||||||
|
/// time taken to generate proof
|
||||||
|
pub time: u32,
|
||||||
|
/// difficulty factor for which the proof was generated
|
||||||
|
pub difficulty_factor: u32,
|
||||||
|
/// worker/client type: wasm, javascript, python, etc.
|
||||||
|
pub worker_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||||
@@ -332,7 +407,6 @@ pub struct Secret {
|
|||||||
/// user's secret
|
/// user's secret
|
||||||
pub secret: String,
|
pub secret: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait to clone MCDatabase
|
/// Trait to clone MCDatabase
|
||||||
pub trait CloneSPDatabase {
|
pub trait CloneSPDatabase {
|
||||||
/// clone DB
|
/// clone DB
|
||||||
|
|||||||
@@ -260,6 +260,60 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||||||
db.record_solve(c.key).await.unwrap();
|
db.record_solve(c.key).await.unwrap();
|
||||||
db.record_confirm(c.key).await.unwrap();
|
db.record_confirm(c.key).await.unwrap();
|
||||||
|
|
||||||
|
// analytics start
|
||||||
|
|
||||||
|
db.analytics_create_psuedo_id_if_not_exists(c.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let psuedo_id = db
|
||||||
|
.analytics_get_psuedo_id_from_capmaign_id(c.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
db.analytics_create_psuedo_id_if_not_exists(c.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
psuedo_id,
|
||||||
|
db.analytics_get_psuedo_id_from_capmaign_id(c.key)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
c.key,
|
||||||
|
db.analytics_get_capmaign_id_from_psuedo_id(&psuedo_id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let analytics = CreatePerformanceAnalytics {
|
||||||
|
time: 0,
|
||||||
|
difficulty_factor: 0,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
};
|
||||||
|
db.analysis_save(c.key, &analytics).await.unwrap();
|
||||||
|
let limit = 50;
|
||||||
|
let mut offset = 0;
|
||||||
|
let a = db.analytics_fetch(c.key, limit, offset).await.unwrap();
|
||||||
|
assert_eq!(a[0].time, analytics.time);
|
||||||
|
assert_eq!(a[0].difficulty_factor, analytics.difficulty_factor);
|
||||||
|
assert_eq!(a[0].worker_type, analytics.worker_type);
|
||||||
|
offset += 1;
|
||||||
|
assert!(db
|
||||||
|
.analytics_fetch(c.key, limit, offset)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
|
db.analytics_delete_all_records_for_campaign(c.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(db.analytics_fetch(c.key, 1000, 0).await.unwrap().len(), 0);
|
||||||
|
assert!(!db.analytics_captcha_is_published(c.key).await.unwrap());
|
||||||
|
db.analytics_delete_all_records_for_campaign(c.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
// analytics end
|
||||||
|
|
||||||
assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1);
|
assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
db.fetch_config_fetched(p.username, c.key)
|
db.fetch_config_fetched(p.username, c.key)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ async-trait = "0.1.51"
|
|||||||
db-core = {path = "../db-core"}
|
db-core = {path = "../db-core"}
|
||||||
futures = "0.3.15"
|
futures = "0.3.15"
|
||||||
sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "mysql", "time", "offline" ] }
|
sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "mysql", "time", "offline" ] }
|
||||||
|
uuid = { version = "1.4.0", features = ["v4", "serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_pow_analytics (
|
||||||
|
ID INT auto_increment,
|
||||||
|
PRIMARY KEY(ID),
|
||||||
|
config_id INTEGER NOT NULL,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
difficulty_factor INTEGER NOT NULL,
|
||||||
|
worker_type VARCHAR(100) NOT NULL,
|
||||||
|
CONSTRAINT `fk_mcaptcha_config_id_pow_analytics`
|
||||||
|
FOREIGN KEY (config_id)
|
||||||
|
REFERENCES mcaptcha_config (config_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_psuedo_campaign_id (
|
||||||
|
ID INT auto_increment,
|
||||||
|
PRIMARY KEY(ID),
|
||||||
|
psuedo_id varchar(100) NOT NULL UNIQUE,
|
||||||
|
config_id INT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT `fk_mcaptcha_psuedo_campaign_id_config_id`
|
||||||
|
FOREIGN KEY (config_id)
|
||||||
|
REFERENCES mcaptcha_config (config_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
|
||||||
|
);
|
||||||
@@ -25,6 +25,31 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT time FROM mcaptcha_pow_confirmed_stats \n WHERE \n config_id = (\n SELECT config_id FROM mcaptcha_config \n WHERE \n captcha_key = ?\n AND\n user_id = (\n SELECT \n ID FROM mcaptcha_users WHERE name = ?))\n ORDER BY time DESC"
|
"query": "SELECT time FROM mcaptcha_pow_confirmed_stats \n WHERE \n config_id = (\n SELECT config_id FROM mcaptcha_config \n WHERE \n captcha_key = ?\n AND\n user_id = (\n SELECT \n ID FROM mcaptcha_users WHERE name = ?))\n ORDER BY time DESC"
|
||||||
},
|
},
|
||||||
|
"14dc89b2988b221fd24e4f319b1d48f5e6c65c760c30d11c9c29087f09cee23a": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "captcha_key",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 224,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4101
|
||||||
|
},
|
||||||
|
"max_size": 400,
|
||||||
|
"type": "VarString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n captcha_key\n FROM\n mcaptcha_config\n WHERE\n config_id = (\n SELECT\n config_id\n FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n psuedo_id = ?\n );"
|
||||||
|
},
|
||||||
"22e697114c3ed5b0156cdceab11a398f1ef3a804f482e1cd948bc615ef95fc92": {
|
"22e697114c3ed5b0156cdceab11a398f1ef3a804f482e1cd948bc615ef95fc92": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -154,6 +179,31 @@
|
|||||||
},
|
},
|
||||||
"query": "INSERT INTO mcaptcha_pow_fetched_stats \n (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)"
|
"query": "INSERT INTO mcaptcha_pow_fetched_stats \n (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)"
|
||||||
},
|
},
|
||||||
|
"5ad1ef722a961183228d851813b9f50284520bf8cc8118c765b72c108daaf6fb": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "psuedo_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 224,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4101
|
||||||
|
},
|
||||||
|
"max_size": 400,
|
||||||
|
"type": "VarString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT psuedo_id FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?));\n "
|
||||||
|
},
|
||||||
"5d5a106981345e9f62bc2239c00cdc683d3aaaa820d63da300dc51e3f6f363d3": {
|
"5d5a106981345e9f62bc2239c00cdc683d3aaaa820d63da300dc51e3f6f363d3": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -164,6 +214,16 @@
|
|||||||
},
|
},
|
||||||
"query": "INSERT INTO mcaptcha_users \n (name , password, secret) VALUES (?, ?, ?)"
|
"query": "INSERT INTO mcaptcha_users \n (name , password, secret) VALUES (?, ?, ?)"
|
||||||
},
|
},
|
||||||
|
"6094468b7fa20043b0da90e366b7f1fa29a8c748e163b6712725440b25ae9361": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n DELETE FROM\n mcaptcha_pow_analytics\n WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?\n ) "
|
||||||
|
},
|
||||||
"66ec7df10484f8e0206f3c97afc9136021589556c38dbbed341d6574487f79f2": {
|
"66ec7df10484f8e0206f3c97afc9136021589556c38dbbed341d6574487f79f2": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -406,6 +466,80 @@
|
|||||||
},
|
},
|
||||||
"query": "UPDATE mcaptcha_users set email = ?\n WHERE name = ?"
|
"query": "UPDATE mcaptcha_users set email = ?\n WHERE name = ?"
|
||||||
},
|
},
|
||||||
|
"9e45969a0f79eab8caba41b0d91e5e3b85a1a68a49136f89fc90793c38f00041": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO\n mcaptcha_psuedo_campaign_id (config_id, psuedo_id)\n VALUES (\n (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)),\n ?\n );"
|
||||||
|
},
|
||||||
|
"9f10afb0f242f11c58389803c5e85e244cc59102b8929a21e3fcaa852d57a52c": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 63,
|
||||||
|
"flags": {
|
||||||
|
"bits": 515
|
||||||
|
},
|
||||||
|
"max_size": 11,
|
||||||
|
"type": "Long"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "time",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 63,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4097
|
||||||
|
},
|
||||||
|
"max_size": 11,
|
||||||
|
"type": "Long"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "difficulty_factor",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 63,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4097
|
||||||
|
},
|
||||||
|
"max_size": 11,
|
||||||
|
"type": "Long"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "worker_type",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 224,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4097
|
||||||
|
},
|
||||||
|
"max_size": 400,
|
||||||
|
"type": "VarString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n id, time, difficulty_factor, worker_type\n FROM\n mcaptcha_pow_analytics\n WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?\n ) \n ORDER BY ID\n LIMIT ? OFFSET ?"
|
||||||
|
},
|
||||||
"a89c066db044cddfdebee6a0fd0d80a5a26dcb7ecc00a9899f5634b72ea0a952": {
|
"a89c066db044cddfdebee6a0fd0d80a5a26dcb7ecc00a9899f5634b72ea0a952": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -759,6 +893,16 @@
|
|||||||
},
|
},
|
||||||
"query": "INSERT INTO mcaptcha_pow_solved_stats \n (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)"
|
"query": "INSERT INTO mcaptcha_pow_solved_stats \n (config_id, time) VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?)"
|
||||||
},
|
},
|
||||||
|
"e4d9bf156a368dcee1433dd5ced9f1991aa15f84e0ade916433aada40f68f0aa": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n DELETE FROM\n mcaptcha_psuedo_campaign_id\n WHERE config_id = (\n SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?\n );"
|
||||||
|
},
|
||||||
"e6569a6064d0e07abea4c0bd4686cdfdaac64f0109ac40efaed06a744a2eaf5e": {
|
"e6569a6064d0e07abea4c0bd4686cdfdaac64f0109ac40efaed06a744a2eaf5e": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -873,6 +1017,16 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT name, password FROM mcaptcha_users WHERE email = ?"
|
"query": "SELECT name, password FROM mcaptcha_users WHERE email = ?"
|
||||||
},
|
},
|
||||||
|
"f987c4568ab28271d87af47f473b18cf41130a483333e81d5f50199758cbb98b": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO mcaptcha_pow_analytics \n (config_id, time, difficulty_factor, worker_type)\n VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?, ?, ?)"
|
||||||
|
},
|
||||||
"fc717ff0827ccfaa1cc61a71cc7f71c348ebb03d35895c54b011c03121ad2385": {
|
"fc717ff0827ccfaa1cc61a71cc7f71c348ebb03d35895c54b011c03121ad2385": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use sqlx::mysql::MySqlPoolOptions;
|
|||||||
use sqlx::types::time::OffsetDateTime;
|
use sqlx::types::time::OffsetDateTime;
|
||||||
use sqlx::ConnectOptions;
|
use sqlx::ConnectOptions;
|
||||||
use sqlx::MySqlPool;
|
use sqlx::MySqlPool;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -73,9 +74,6 @@ impl Connect for ConnectionOptions {
|
|||||||
if fresh.disable_logging {
|
if fresh.disable_logging {
|
||||||
connect_options.disable_statement_logging();
|
connect_options.disable_statement_logging();
|
||||||
}
|
}
|
||||||
sqlx::mysql::MySqlConnectOptions::from_str(&fresh.url)
|
|
||||||
.unwrap()
|
|
||||||
.disable_statement_logging();
|
|
||||||
fresh
|
fresh
|
||||||
.pool_options
|
.pool_options
|
||||||
.connect_with(connect_options)
|
.connect_with(connect_options)
|
||||||
@@ -898,6 +896,191 @@ impl MCDatabase for Database {
|
|||||||
|
|
||||||
Ok(Date::dates_to_unix(records))
|
Ok(Date::dates_to_unix(records))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// record PoW timing
|
||||||
|
async fn analysis_save(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
d: &CreatePerformanceAnalytics,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"INSERT INTO mcaptcha_pow_analytics
|
||||||
|
(config_id, time, difficulty_factor, worker_type)
|
||||||
|
VALUES ((SELECT config_id FROM mcaptcha_config where captcha_key= ?), ?, ?, ?)",
|
||||||
|
captcha_id,
|
||||||
|
d.time as i32,
|
||||||
|
d.difficulty_factor as i32,
|
||||||
|
&d.worker_type,
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fetch PoW analytics
|
||||||
|
async fn analytics_fetch(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<PerformanceAnalytics>> {
|
||||||
|
struct P {
|
||||||
|
id: i32,
|
||||||
|
time: i32,
|
||||||
|
difficulty_factor: i32,
|
||||||
|
worker_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<P> for PerformanceAnalytics {
|
||||||
|
fn from(v: P) -> Self {
|
||||||
|
Self {
|
||||||
|
id: v.id as usize,
|
||||||
|
time: v.time as u32,
|
||||||
|
difficulty_factor: v.difficulty_factor as u32,
|
||||||
|
worker_type: v.worker_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut c = sqlx::query_as!(
|
||||||
|
P,
|
||||||
|
"SELECT
|
||||||
|
id, time, difficulty_factor, worker_type
|
||||||
|
FROM
|
||||||
|
mcaptcha_pow_analytics
|
||||||
|
WHERE
|
||||||
|
config_id = (
|
||||||
|
SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
|
||||||
|
)
|
||||||
|
ORDER BY ID
|
||||||
|
LIMIT ? OFFSET ?",
|
||||||
|
&captcha_id,
|
||||||
|
limit as i64,
|
||||||
|
offset as i64,
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
let mut res = Vec::with_capacity(c.len());
|
||||||
|
for i in c.drain(0..) {
|
||||||
|
res.push(i.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create psuedo ID against campaign ID to publish analytics
|
||||||
|
async fn analytics_create_psuedo_id_if_not_exists(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT INTO
|
||||||
|
mcaptcha_psuedo_campaign_id (config_id, psuedo_id)
|
||||||
|
VALUES (
|
||||||
|
(SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?)),
|
||||||
|
?
|
||||||
|
);",
|
||||||
|
captcha_id,
|
||||||
|
&id.to_string(),
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get psuedo ID from campaign ID
|
||||||
|
async fn analytics_get_psuedo_id_from_capmaign_id(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
) -> DBResult<String> {
|
||||||
|
struct ID {
|
||||||
|
psuedo_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = sqlx::query_as!(
|
||||||
|
ID,
|
||||||
|
"SELECT psuedo_id FROM
|
||||||
|
mcaptcha_psuedo_campaign_id
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = (?));
|
||||||
|
",
|
||||||
|
captcha_id
|
||||||
|
).fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(res.psuedo_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get campaign ID from psuedo ID
|
||||||
|
async fn analytics_get_capmaign_id_from_psuedo_id(
|
||||||
|
&self,
|
||||||
|
psuedo_id: &str,
|
||||||
|
) -> DBResult<String> {
|
||||||
|
struct ID {
|
||||||
|
captcha_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = sqlx::query_as!(
|
||||||
|
ID,
|
||||||
|
"SELECT
|
||||||
|
captcha_key
|
||||||
|
FROM
|
||||||
|
mcaptcha_config
|
||||||
|
WHERE
|
||||||
|
config_id = (
|
||||||
|
SELECT
|
||||||
|
config_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_psuedo_campaign_id
|
||||||
|
WHERE
|
||||||
|
psuedo_id = ?
|
||||||
|
);",
|
||||||
|
psuedo_id
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
Ok(res.captcha_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn analytics_delete_all_records_for_campaign(
|
||||||
|
&self,
|
||||||
|
campaign_id: &str,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM
|
||||||
|
mcaptcha_psuedo_campaign_id
|
||||||
|
WHERE config_id = (
|
||||||
|
SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
|
||||||
|
);",
|
||||||
|
campaign_id
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM
|
||||||
|
mcaptcha_pow_analytics
|
||||||
|
WHERE
|
||||||
|
config_id = (
|
||||||
|
SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?
|
||||||
|
) ",
|
||||||
|
campaign_id
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ async-trait = "0.1.51"
|
|||||||
db-core = {path = "../db-core"}
|
db-core = {path = "../db-core"}
|
||||||
futures = "0.3.15"
|
futures = "0.3.15"
|
||||||
sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] }
|
sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] }
|
||||||
|
uuid = { version = "1.4.0", features = ["v4", "serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2"
|
actix-rt = "2"
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_pow_analytics (
|
||||||
|
config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE,
|
||||||
|
time INTEGER NOT NULL,
|
||||||
|
difficulty_factor INTEGER NOT NULL,
|
||||||
|
worker_type VARCHAR(100) NOT NULL,
|
||||||
|
ID SERIAL PRIMARY KEY NOT NULL
|
||||||
|
);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_psuedo_campaign_id (
|
||||||
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
config_id INTEGER NOT NULL references mcaptcha_config(config_id) ON DELETE CASCADE,
|
||||||
|
psuedo_id varchar(100) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
@@ -1,5 +1,45 @@
|
|||||||
{
|
{
|
||||||
"db": "PostgreSQL",
|
"db": "PostgreSQL",
|
||||||
|
"017576128f1c63aee062799a33f872457fe19f5d6429d0af312dc00c244b31cb": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "time",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "difficulty_factor",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "worker_type",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT id, time, difficulty_factor, worker_type FROM mcaptcha_pow_analytics\n WHERE \n config_id = (\n SELECT \n config_id FROM mcaptcha_config \n WHERE \n key = $1\n )\n ORDER BY ID\n OFFSET $2 LIMIT $3\n "
|
||||||
|
},
|
||||||
"02deb524bb12632af9b7883975f75fdc30d6775d836aff647add1dffd1a4bc00": {
|
"02deb524bb12632af9b7883975f75fdc30d6775d836aff647add1dffd1a4bc00": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -132,6 +172,26 @@
|
|||||||
},
|
},
|
||||||
"query": "UPDATE mcaptcha_users set name = $1\n WHERE name = $2"
|
"query": "UPDATE mcaptcha_users set name = $1\n WHERE name = $2"
|
||||||
},
|
},
|
||||||
|
"21cdf28d8962389d22c8ddefdad82780f5316737e3d833623512aa12a54a026a": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n key\n FROM\n mcaptcha_config\n WHERE\n config_id = (\n SELECT\n config_id\n FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n psuedo_id = $1\n );"
|
||||||
|
},
|
||||||
"2b319a202bb983d5f28979d1e371f399125da1122fbda36a5a55b75b9c743451": {
|
"2b319a202bb983d5f28979d1e371f399125da1122fbda36a5a55b75b9c743451": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -180,6 +240,18 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT email FROM mcaptcha_users WHERE name = $1"
|
"query": "SELECT email FROM mcaptcha_users WHERE name = $1"
|
||||||
},
|
},
|
||||||
|
"30d8945806b4c68b6da800395f61c1e480839093bfcda9c693bf1972a65c7d79": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n DELETE FROM\n mcaptcha_psuedo_campaign_id\n WHERE config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = ($1)\n );"
|
||||||
|
},
|
||||||
"3b1c8128fc48b16d8e8ea6957dd4fbc0eb19ae64748fd7824e9f5e1901dd1726": {
|
"3b1c8128fc48b16d8e8ea6957dd4fbc0eb19ae64748fd7824e9f5e1901dd1726": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -406,6 +478,26 @@
|
|||||||
},
|
},
|
||||||
"query": "INSERT INTO mcaptcha_users \n (name , password, secret) VALUES ($1, $2, $3)"
|
"query": "INSERT INTO mcaptcha_users \n (name , password, secret) VALUES ($1, $2, $3)"
|
||||||
},
|
},
|
||||||
|
"839dfdfc3543b12128cb2b44bf356cd81f3da380963e5684ec3624a0ea4f9547": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "psuedo_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT psuedo_id FROM\n mcaptcha_psuedo_campaign_id\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1));\n "
|
||||||
|
},
|
||||||
"84484cb6892db29121816bc5bff5702b9e857e20aa14e79d080d78ae7593153b": {
|
"84484cb6892db29121816bc5bff5702b9e857e20aa14e79d080d78ae7593153b": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -493,6 +585,33 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE name = $1)"
|
"query": "SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE name = $1)"
|
||||||
},
|
},
|
||||||
|
"af47990880a92c63d1cf5192203899c72621479dc6bb47859fb4498264b78033": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int4",
|
||||||
|
"Int4",
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO mcaptcha_pow_analytics \n (config_id, time, difficulty_factor, worker_type)\n VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2, $3, $4)"
|
||||||
|
},
|
||||||
|
"b67da576ff30a1bc8b1c0a79eff07f0622bd9ea035d3de15b91f5e1e8a5fda9b": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n DELETE FROM\n mcaptcha_pow_analytics\n WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = $1\n )\n "
|
||||||
|
},
|
||||||
"b97d810814fbeb2df19f47bcfa381bc6fb7ac6832d040b377cf4fca2ca896cfb": {
|
"b97d810814fbeb2df19f47bcfa381bc6fb7ac6832d040b377cf4fca2ca896cfb": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -545,6 +664,19 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT name, password FROM mcaptcha_users WHERE email = ($1)"
|
"query": "SELECT name, password FROM mcaptcha_users WHERE email = ($1)"
|
||||||
},
|
},
|
||||||
|
"c1bb8e02d1f9dc28322309d055de3c40ed4e1a1b9453a7e5a93a70e5186d762d": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "\n INSERT INTO\n mcaptcha_psuedo_campaign_id (config_id, psuedo_id)\n VALUES (\n (SELECT config_id FROM mcaptcha_config WHERE key = ($1)),\n $2\n );"
|
||||||
|
},
|
||||||
"c2e167e56242de7e0a835e25004b15ca8340545fa0ca7ac8f3293157d2d03d98": {
|
"c2e167e56242de7e0a835e25004b15ca8340545fa0ca7ac8f3293157d2d03d98": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use sqlx::postgres::PgPoolOptions;
|
|||||||
use sqlx::types::time::OffsetDateTime;
|
use sqlx::types::time::OffsetDateTime;
|
||||||
use sqlx::ConnectOptions;
|
use sqlx::ConnectOptions;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -73,9 +74,6 @@ impl Connect for ConnectionOptions {
|
|||||||
if fresh.disable_logging {
|
if fresh.disable_logging {
|
||||||
connect_options.disable_statement_logging();
|
connect_options.disable_statement_logging();
|
||||||
}
|
}
|
||||||
sqlx::postgres::PgConnectOptions::from_str(&fresh.url)
|
|
||||||
.unwrap()
|
|
||||||
.disable_statement_logging();
|
|
||||||
fresh
|
fresh
|
||||||
.pool_options
|
.pool_options
|
||||||
.connect_with(connect_options)
|
.connect_with(connect_options)
|
||||||
@@ -904,6 +902,194 @@ impl MCDatabase for Database {
|
|||||||
|
|
||||||
Ok(Date::dates_to_unix(records))
|
Ok(Date::dates_to_unix(records))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// record PoW timing
|
||||||
|
async fn analysis_save(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
d: &CreatePerformanceAnalytics,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"INSERT INTO mcaptcha_pow_analytics
|
||||||
|
(config_id, time, difficulty_factor, worker_type)
|
||||||
|
VALUES ((SELECT config_id FROM mcaptcha_config WHERE key = $1), $2, $3, $4)",
|
||||||
|
captcha_id,
|
||||||
|
d.time as i32,
|
||||||
|
d.difficulty_factor as i32,
|
||||||
|
&d.worker_type,
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fetch PoW analytics
|
||||||
|
async fn analytics_fetch(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<PerformanceAnalytics>> {
|
||||||
|
struct P {
|
||||||
|
id: i32,
|
||||||
|
time: i32,
|
||||||
|
difficulty_factor: i32,
|
||||||
|
worker_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<P> for PerformanceAnalytics {
|
||||||
|
fn from(v: P) -> Self {
|
||||||
|
Self {
|
||||||
|
time: v.time as u32,
|
||||||
|
difficulty_factor: v.difficulty_factor as u32,
|
||||||
|
worker_type: v.worker_type,
|
||||||
|
id: v.id as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut c = sqlx::query_as!(
|
||||||
|
P,
|
||||||
|
"SELECT id, time, difficulty_factor, worker_type FROM mcaptcha_pow_analytics
|
||||||
|
WHERE
|
||||||
|
config_id = (
|
||||||
|
SELECT
|
||||||
|
config_id FROM mcaptcha_config
|
||||||
|
WHERE
|
||||||
|
key = $1
|
||||||
|
)
|
||||||
|
ORDER BY ID
|
||||||
|
OFFSET $2 LIMIT $3
|
||||||
|
",
|
||||||
|
&captcha_id,
|
||||||
|
offset as i32,
|
||||||
|
limit as i32
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
let mut res = Vec::with_capacity(c.len());
|
||||||
|
for i in c.drain(0..) {
|
||||||
|
res.push(i.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create psuedo ID against campaign ID to publish analytics
|
||||||
|
async fn analytics_create_psuedo_id_if_not_exists(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT INTO
|
||||||
|
mcaptcha_psuedo_campaign_id (config_id, psuedo_id)
|
||||||
|
VALUES (
|
||||||
|
(SELECT config_id FROM mcaptcha_config WHERE key = ($1)),
|
||||||
|
$2
|
||||||
|
);",
|
||||||
|
captcha_id,
|
||||||
|
&id.to_string(),
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get psuedo ID from campaign ID
|
||||||
|
async fn analytics_get_psuedo_id_from_capmaign_id(
|
||||||
|
&self,
|
||||||
|
captcha_id: &str,
|
||||||
|
) -> DBResult<String> {
|
||||||
|
struct ID {
|
||||||
|
psuedo_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = sqlx::query_as!(
|
||||||
|
ID,
|
||||||
|
"SELECT psuedo_id FROM
|
||||||
|
mcaptcha_psuedo_campaign_id
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1));
|
||||||
|
",
|
||||||
|
captcha_id
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(res.psuedo_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get campaign ID from psuedo ID
|
||||||
|
async fn analytics_get_capmaign_id_from_psuedo_id(
|
||||||
|
&self,
|
||||||
|
psuedo_id: &str,
|
||||||
|
) -> DBResult<String> {
|
||||||
|
struct ID {
|
||||||
|
key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = sqlx::query_as!(
|
||||||
|
ID,
|
||||||
|
"SELECT
|
||||||
|
key
|
||||||
|
FROM
|
||||||
|
mcaptcha_config
|
||||||
|
WHERE
|
||||||
|
config_id = (
|
||||||
|
SELECT
|
||||||
|
config_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_psuedo_campaign_id
|
||||||
|
WHERE
|
||||||
|
psuedo_id = $1
|
||||||
|
);",
|
||||||
|
psuedo_id
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
Ok(res.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn analytics_delete_all_records_for_campaign(
|
||||||
|
&self,
|
||||||
|
campaign_id: &str,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM
|
||||||
|
mcaptcha_psuedo_campaign_id
|
||||||
|
WHERE config_id = (
|
||||||
|
SELECT config_id FROM mcaptcha_config WHERE key = ($1)
|
||||||
|
);",
|
||||||
|
campaign_id
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM
|
||||||
|
mcaptcha_pow_analytics
|
||||||
|
WHERE
|
||||||
|
config_id = (
|
||||||
|
SELECT config_id FROM mcaptcha_config WHERE key = $1
|
||||||
|
)
|
||||||
|
",
|
||||||
|
campaign_id
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ you will be overriding the values set in the configuration files.
|
|||||||
| `MCAPTCHA_DATEBASE_USERNAME` | database username |
|
| `MCAPTCHA_DATEBASE_USERNAME` | database username |
|
||||||
| `MCAPTCHA_DATEBASE_POOL` | database connection pool size |
|
| `MCAPTCHA_DATEBASE_POOL` | database connection pool size |
|
||||||
| `MCAPTCHA_DATEBASE_DATABASE_TYPE` | database tpye: "postgres" or "maria" |
|
| `MCAPTCHA_DATEBASE_DATABASE_TYPE` | database tpye: "postgres" or "maria" |
|
||||||
| `DATABSE_URL` (overrides above vars) | database URL in `postgres://user:pass@host:port/dbname` format |
|
| `DATABASE_URL` (overrides above vars) | database URL in `postgres://user:pass@host:port/dbname` format |
|
||||||
|
|
||||||
#### Redis
|
#### Redis
|
||||||
|
|
||||||
|
|||||||
@@ -1799,9 +1799,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
|
|||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@^1.2.5:
|
minimist@^1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
|
|
||||||
mkdirp@^1.0.4:
|
mkdirp@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
|
|||||||
103
docs/third-party/NGIZero-green.hex.svg
vendored
Normal file
103
docs/third-party/NGIZero-green.hex.svg
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
id="Ebene_1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="165.92125"
|
||||||
|
height="191.45087"
|
||||||
|
viewBox="0 0 165.92125 191.45086"
|
||||||
|
enable-background="new 0 0 198.425 198.425"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="NGIZero-green.svg"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||||
|
id="metadata4142"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs4140" /><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1007"
|
||||||
|
id="namedview4138"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.6820179"
|
||||||
|
inkscape:cx="-191.39267"
|
||||||
|
inkscape:cy="54.855534"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="Ebene_1"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0" />
|
||||||
|
<polygon
|
||||||
|
points="36.911,63.104 36.911,66.116 36.911,132.309 36.911,135.321 39.346,136.825 96.715,169.921 99.273,171.419 101.853,169.921 159.319,136.825 161.938,135.321 161.938,132.309 161.938,66.116 161.938,63.104 159.308,61.6 101.841,28.504 99.234,27.006 96.629,28.504 39.347,61.6 "
|
||||||
|
id="polygon4013"
|
||||||
|
style="fill:#96c00a;fill-opacity:1"
|
||||||
|
transform="matrix(1.3249745,0,0,1.3249745,-48.642464,-35.674938)" />
|
||||||
|
<polygon
|
||||||
|
points="161.712,62.925 161.712,131.589 99.212,167.589 36.712,131.589 36.712,62.925 99.212,26.925 "
|
||||||
|
id="polygon4015"
|
||||||
|
style="fill:#97bf00;fill-opacity:0.91764706"
|
||||||
|
transform="matrix(1.3249745,0,0,1.3249745,-48.642464,-35.674938)" />
|
||||||
|
<polygon
|
||||||
|
stroke-miterlimit="10"
|
||||||
|
points="157.712,65.379 157.712,133.046 99.212,166.88 40.712,133.046 40.712,65.379 99.212,31.546 "
|
||||||
|
id="Outerline"
|
||||||
|
transform="matrix(1.3249745,0,0,1.3249745,-48.642464,-35.674938)"
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-miterlimit:10"
|
||||||
|
inkscape:label="#outerline" />
|
||||||
|
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g4281"
|
||||||
|
transform="matrix(1.3249745,0,0,1.3249745,-47.067006,-23.859001)"><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path42"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.55783975"
|
||||||
|
d="m 133.45691,60.461638 v 0 c 2.27263,0 4.11462,1.841988 4.11462,4.114628 v 27.330241 c 0,2.27264 -1.84199,4.114628 -4.11462,4.114628 -2.27264,0 -4.11463,-1.841988 -4.11463,-4.114628 V 64.576266 c 0,-2.27264 1.84199,-4.114628 4.11463,-4.114628" /><g
|
||||||
|
transform="matrix(0.55783976,0,0,-0.55783976,120.13631,77.682765)"
|
||||||
|
id="g44"><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path46"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="M 0,0 H -0.506 C -0.57,0 -0.633,-0.008 -0.698,-0.01 -0.762,-0.008 -0.825,0 -0.89,0 h -7.283 c -3.929,0 -7.359,-2.965 -7.613,-6.885 -0.278,-4.296 3.124,-7.867 7.361,-7.867 0.776,0 1.343,-0.754 1.111,-1.494 -0.658,-2.088 -2.341,-3.751 -4.547,-4.333 -2.074,-0.547 -4.276,-0.821 -6.605,-0.821 -4.007,0 -7.574,0.865 -10.7,2.595 -3.127,1.73 -5.57,4.144 -7.331,7.24 -1.761,3.096 -2.641,6.617 -2.641,10.564 0,4.006 0.88,7.558 2.641,10.654 1.761,3.097 4.219,5.493 7.377,7.195 3.156,1.698 6.768,2.549 10.836,2.549 4.681,0 8.865,-1.269 12.55,-3.807 2.341,-1.612 5.524,-1.588 7.757,0.171 3.48,2.741 3.289,8.045 -0.315,10.452 -1.7,1.136 -3.538,2.112 -5.512,2.928 -4.553,1.881 -9.623,2.823 -15.208,2.823 -6.679,0 -12.69,-1.412 -18.03,-4.235 -5.344,-2.822 -9.517,-6.738 -12.522,-11.747 -3.005,-5.008 -4.508,-10.67 -4.508,-16.983 0,-6.315 1.503,-11.975 4.508,-16.984 3.005,-5.009 7.148,-8.924 12.43,-11.747 5.282,-2.824 11.231,-4.235 17.849,-4.235 4.613,0 9.197,0.699 13.751,2.095 0.045,0.014 0.091,0.028 0.136,0.042 7.104,2.202 11.884,8.86 11.884,16.297 v 9.047 C 6.486,-2.904 3.583,0 0,0" /></g><g
|
||||||
|
transform="matrix(0.55783976,0,0,-0.55783976,85.80763,64.525332)"
|
||||||
|
id="g48"><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path50"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||||
|
d="m 0,0 v -49.176 c 0,-4.023 -3.262,-7.285 -7.286,-7.285 h -1.381 c -2.181,0 -4.247,0.977 -5.631,2.662 l -24.229,29.505 c -1.804,2.197 -5.368,0.921 -5.368,-1.922 v -22.96 c 0,-4.023 -3.261,-7.285 -7.285,-7.285 -4.023,0 -7.285,3.262 -7.285,7.285 V 0 c 0,4.024 3.262,7.285 7.285,7.285 h 1.468 c 2.184,0 4.253,-0.979 5.636,-2.669 l 24.135,-29.475 c 1.802,-2.202 5.37,-0.927 5.37,1.918 V 0 c 0,4.024 3.261,7.285 7.285,7.285 C -3.262,7.285 0,4.024 0,0" /></g></g><g
|
||||||
|
aria-label="Z E R O"
|
||||||
|
transform="matrix(0.94681934,0,0,0.94681934,-209.97267,182.03385)"
|
||||||
|
style="font-variant:normal;font-weight:600;font-stretch:normal;font-size:31.76000023px;font-family:'Montserrat SemiBold';-inkscape-font-specification:Montserrat-SemiBold;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:0.7171717;fill-rule:nonzero;stroke:none"
|
||||||
|
id="text56"><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 243.58117,-73.015206 h 19.46231 v 3.613321 l -12.42176,15.02707 h 12.77844 v 4.512774 h -20.17567 v -3.613321 l 12.42176,-15.02707 h -12.06508 z"
|
||||||
|
id="path2325" /><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 278.7684,-73.015206 h 16.11262 v 4.512774 h -10.14211 v 4.311172 h 9.5373 v 4.512773 h -9.5373 v 5.303672 h 10.48328 v 4.512774 H 278.7684 Z"
|
||||||
|
id="path2327" /><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 320.00367,-62.749034 q 1.87645,0 2.68285,-0.697851 0.82192,-0.697852 0.82192,-2.295157 0,-1.581796 -0.82192,-2.26414 -0.8064,-0.682344 -2.68285,-0.682344 h -2.51226 v 5.939492 z m -2.51226,4.125078 v 8.761915 h -5.97051 v -23.153165 h 9.11859 q 4.57481,0 6.69938,1.535274 2.14008,1.535273 2.14008,4.853945 0,2.295156 -1.11657,3.768399 -1.10105,1.473242 -3.33418,2.171093 1.22512,0.279141 2.18661,1.271641 0.97699,0.976992 1.96949,2.9775 l 3.24113,6.575313 h -6.3582 l -2.82242,-5.753399 q -0.85293,-1.736875 -1.73688,-2.372695 -0.86844,-0.635821 -2.32617,-0.635821 z"
|
||||||
|
id="path2329" /><path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 357.57911,-69.107237 q -2.72938,0 -4.23364,2.016016 -1.50425,2.016015 -1.50425,5.675859 0,3.644336 1.50425,5.660352 1.50426,2.016015 4.23364,2.016015 2.74488,0 4.24914,-2.016015 1.50426,-2.016016 1.50426,-5.660352 0,-3.659844 -1.50426,-5.675859 -1.50426,-2.016016 -4.24914,-2.016016 z m 0,-4.32668 q 5.58281,0 8.7464,3.19461 3.1636,3.194609 3.1636,8.823945 0,5.613828 -3.1636,8.808438 -3.16359,3.194609 -8.7464,3.194609 -5.56731,0 -8.74641,-3.194609 -3.16359,-3.19461 -3.16359,-8.808438 0,-5.629336 3.16359,-8.823945 3.1791,-3.19461 8.74641,-3.19461 z"
|
||||||
|
id="path2331" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -136,7 +136,9 @@ export default {
|
|||||||
testEnvironment: "jest-environment-jsdom",
|
testEnvironment: "jest-environment-jsdom",
|
||||||
|
|
||||||
// Options that will be passed to the testEnvironment
|
// Options that will be passed to the testEnvironment
|
||||||
// testEnvironmentOptions: {},
|
testEnvironmentOptions: {
|
||||||
|
"url": "http://localhost:7000/widget/?sitekey=imbatman"
|
||||||
|
},
|
||||||
|
|
||||||
// Adds a location field to test results
|
// Adds a location field to test results
|
||||||
// testLocationInResults: false,
|
// testLocationInResults: false,
|
||||||
|
|||||||
19224
package-lock.json
generated
Normal file
19224
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@@ -10,34 +10,35 @@
|
|||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/jsdom": "^16.2.10",
|
"@types/jsdom": "^21.1.1",
|
||||||
"@types/node": "^16.10.4",
|
"@types/node": "^20.3.3",
|
||||||
"@types/sinon": "^10.0.0",
|
"@types/sinon": "^10.0.15",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
"@typescript-eslint/parser": "^5.60.1",
|
||||||
"@wasm-tool/wasm-pack-plugin": "^1.4.0",
|
"@wasm-tool/wasm-pack-plugin": "^1.7.0",
|
||||||
"css-loader": "^6.4.0",
|
"css-loader": "^6.8.1",
|
||||||
"css-minimizer-webpack-plugin": "^3.1.1",
|
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||||
"sass": "^1.25.0",
|
"eslint": "^8.44.0",
|
||||||
"eslint": "^8.0.0",
|
"jest": "^29.5.0",
|
||||||
"jest": "^27.2.5",
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"jsdom": "^18.0.0",
|
"jsdom": "^22.1.0",
|
||||||
"mini-css-extract-plugin": "^2.4.2",
|
"mini-css-extract-plugin": "^2.7.6",
|
||||||
"sass-loader": "^12.2.0",
|
"sass": "^1.63.6",
|
||||||
"sinon": "^11.1.2",
|
"sass-loader": "^13.3.2",
|
||||||
"ts-jest": "^27.0.5",
|
"sinon": "^15.2.0",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-jest": "^29.1.1",
|
||||||
"ts-node": "^10.3.0",
|
"ts-loader": "^9.4.4",
|
||||||
"typescript": "^4.1.0",
|
"ts-node": "^10.9.1",
|
||||||
"webpack": "^5.0.0",
|
"typescript": "^5.1.6",
|
||||||
"webpack-cli": "^4.6.0",
|
"webpack": "^5.88.1",
|
||||||
"webpack-dev-server": "^4.3.1"
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-dev-server": "^4.15.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mcaptcha/pow-wasm": "^0.1.0-alpha-1",
|
|
||||||
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-alpha-1",
|
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-alpha-1",
|
||||||
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-1"
|
"@mcaptcha/pow-wasm": "^0.1.0-alpha-1",
|
||||||
|
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
scripts/integration.sh
Executable file
39
scripts/integration.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
trap cleanup SIGINT SIGTERM ERR EXIT
|
||||||
|
|
||||||
|
readonly PROJECT_ROOT=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||||
|
|
||||||
|
source $PROJECT_ROOT/scripts/lib.sh
|
||||||
|
|
||||||
|
is_ci(){
|
||||||
|
if [ -z ${CI+x} ];
|
||||||
|
then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
docker-compose down -v --remove-orphans || true
|
||||||
|
docker-compose up -d
|
||||||
|
cd $(mktemp -d)
|
||||||
|
pwd
|
||||||
|
find
|
||||||
|
git clone https://github.com/mCaptcha/integration .
|
||||||
|
|
||||||
|
if is_ci
|
||||||
|
then
|
||||||
|
yarn install
|
||||||
|
xvfb-run --auto-servernum npm run test.firefox
|
||||||
|
xvfb-run --auto-servernum npm run test.chrome
|
||||||
|
else
|
||||||
|
yarn install
|
||||||
|
npx nightwatch ./test/mCaptcha.ts
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $PROJECT_ROOT
|
||||||
|
docker-compose down -v --remove-orphans || true
|
||||||
@@ -97,14 +97,15 @@ delete_dir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
upload_dist() {
|
upload_dist() {
|
||||||
delete_dir $1
|
upload_dir="mCaptcha/$1"
|
||||||
|
delete_dir $upload_dir
|
||||||
|
|
||||||
pushd $TMP_DIR
|
pushd $TMP_DIR
|
||||||
for file in $TARBALL $TARBALL.asc $TARBALL.sha256
|
for file in $TARBALL $TARBALL.asc $TARBALL.sha256
|
||||||
do
|
do
|
||||||
curl -v \
|
curl -v \
|
||||||
-F upload=@$file \
|
-F upload=@$file \
|
||||||
"$DUMBSERVE_HOST/api/v1/files/upload?path=$1/"
|
"$DUMBSERVE_HOST/api/v1/files/upload?path=$upload_dir"
|
||||||
done
|
done
|
||||||
popd
|
popd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub struct CreateCaptcha {
|
|||||||
pub levels: Vec<Level>,
|
pub levels: Vec<Level>,
|
||||||
pub duration: u32,
|
pub duration: u32,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
pub publish_benchmarks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
@@ -52,6 +53,11 @@ pub async fn create(
|
|||||||
) -> ServiceResult<impl Responder> {
|
) -> ServiceResult<impl Responder> {
|
||||||
let username = id.identity().unwrap();
|
let username = id.identity().unwrap();
|
||||||
let mcaptcha_config = runner::create(&payload, &data, &username).await?;
|
let mcaptcha_config = runner::create(&payload, &data, &username).await?;
|
||||||
|
if payload.publish_benchmarks {
|
||||||
|
data.db
|
||||||
|
.analytics_create_psuedo_id_if_not_exists(&mcaptcha_config.key)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
Ok(HttpResponse::Ok().json(mcaptcha_config))
|
Ok(HttpResponse::Ok().json(mcaptcha_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ pub struct TrafficPatternRequest {
|
|||||||
pub broke_my_site_traffic: Option<u32>,
|
pub broke_my_site_traffic: Option<u32>,
|
||||||
/// Captcha description
|
/// Captcha description
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
||||||
|
/// publish benchmarks
|
||||||
|
pub publish_benchmarks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&TrafficPatternRequest> for TrafficPattern {
|
impl From<&TrafficPatternRequest> for TrafficPattern {
|
||||||
@@ -127,12 +130,14 @@ async fn create(
|
|||||||
levels,
|
levels,
|
||||||
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
|
publish_benchmarks: payload.publish_benchmarks,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mcaptcha_config = create_runner(&msg, &data, &username).await?;
|
let mcaptcha_config = create_runner(&msg, &data, &username).await?;
|
||||||
data.db
|
data.db
|
||||||
.add_traffic_pattern(&username, &mcaptcha_config.key, &pattern)
|
.add_traffic_pattern(&username, &mcaptcha_config.key, &pattern)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(mcaptcha_config))
|
Ok(HttpResponse::Ok().json(mcaptcha_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +167,7 @@ async fn update(
|
|||||||
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
||||||
description: payload.pattern.description,
|
description: payload.pattern.description,
|
||||||
key: payload.key,
|
key: payload.key,
|
||||||
|
publish_benchmarks: payload.pattern.publish_benchmarks,
|
||||||
};
|
};
|
||||||
|
|
||||||
update_captcha_runner(&msg, &data, &username).await?;
|
update_captcha_runner(&msg, &data, &username).await?;
|
||||||
@@ -292,6 +298,7 @@ pub mod tests {
|
|||||||
peak_sustainable_traffic: 1_000_000,
|
peak_sustainable_traffic: 1_000_000,
|
||||||
broke_my_site_traffic: Some(10_000_000),
|
broke_my_site_traffic: Some(10_000_000),
|
||||||
description: NAME.into(),
|
description: NAME.into(),
|
||||||
|
publish_benchmarks: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let default_levels = calculate(
|
let default_levels = calculate(
|
||||||
@@ -323,6 +330,11 @@ pub mod tests {
|
|||||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||||
let res_levels: Vec<Level> = test::read_body_json(get_level_resp).await;
|
let res_levels: Vec<Level> = test::read_body_json(get_level_resp).await;
|
||||||
assert_eq!(res_levels, default_levels);
|
assert_eq!(res_levels, default_levels);
|
||||||
|
assert!(!data
|
||||||
|
.db
|
||||||
|
.analytics_captcha_is_published(&token_key.key)
|
||||||
|
.await
|
||||||
|
.unwrap());
|
||||||
// END create_easy
|
// END create_easy
|
||||||
|
|
||||||
// START update_easy
|
// START update_easy
|
||||||
@@ -331,6 +343,7 @@ pub mod tests {
|
|||||||
peak_sustainable_traffic: 10_000,
|
peak_sustainable_traffic: 10_000,
|
||||||
broke_my_site_traffic: Some(1_000_000),
|
broke_my_site_traffic: Some(1_000_000),
|
||||||
description: NAME.into(),
|
description: NAME.into(),
|
||||||
|
publish_benchmarks: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let updated_default_values = calculate(
|
let updated_default_values = calculate(
|
||||||
@@ -352,6 +365,11 @@ pub mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(update_token_resp.status(), StatusCode::OK);
|
assert_eq!(update_token_resp.status(), StatusCode::OK);
|
||||||
|
assert!(data
|
||||||
|
.db
|
||||||
|
.analytics_captcha_is_published(&token_key.key)
|
||||||
|
.await
|
||||||
|
.unwrap());
|
||||||
|
|
||||||
let get_level_resp = test::call_service(
|
let get_level_resp = test::call_service(
|
||||||
&app,
|
&app,
|
||||||
@@ -394,5 +412,52 @@ pub mod tests {
|
|||||||
));
|
));
|
||||||
assert!(body.contains(&payload.pattern.avg_traffic.to_string()));
|
assert!(body.contains(&payload.pattern.avg_traffic.to_string()));
|
||||||
assert!(body.contains(&payload.pattern.peak_sustainable_traffic.to_string()));
|
assert!(body.contains(&payload.pattern.peak_sustainable_traffic.to_string()));
|
||||||
|
|
||||||
|
// START update_easy to delete published results
|
||||||
|
let mut payload2 = TrafficPatternRequest {
|
||||||
|
avg_traffic: 100_000,
|
||||||
|
peak_sustainable_traffic: 1_000_000,
|
||||||
|
broke_my_site_traffic: Some(10_000_000),
|
||||||
|
description: NAME.into(),
|
||||||
|
publish_benchmarks: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let add_token_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&payload2, ROUTES.captcha.easy.create)
|
||||||
|
.cookie(cookies.clone())
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(add_token_resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
assert!(data
|
||||||
|
.db
|
||||||
|
.analytics_captcha_is_published(&token_key.key)
|
||||||
|
.await
|
||||||
|
.unwrap());
|
||||||
|
|
||||||
|
let token_key2: MCaptchaDetails = test::read_body_json(add_token_resp).await;
|
||||||
|
|
||||||
|
payload2.publish_benchmarks = false;
|
||||||
|
|
||||||
|
let payload = UpdateTrafficPattern {
|
||||||
|
pattern: payload2,
|
||||||
|
key: token_key2.key.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let update_token_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&payload, ROUTES.captcha.easy.update)
|
||||||
|
.cookie(cookies.clone())
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(update_token_resp.status(), StatusCode::OK);
|
||||||
|
assert!(!data
|
||||||
|
.db
|
||||||
|
.analytics_captcha_is_published(&token_key2.key)
|
||||||
|
.await
|
||||||
|
.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ pub async fn level_routes_work(data: ArcData) {
|
|||||||
levels: levels.clone(),
|
levels: levels.clone(),
|
||||||
description: add_level.description,
|
description: add_level.description,
|
||||||
duration: add_level.duration,
|
duration: add_level.duration,
|
||||||
|
publish_benchmarks: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let add_token_resp = test::call_service(
|
let add_token_resp = test::call_service(
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ pub struct UpdateCaptcha {
|
|||||||
pub duration: u32,
|
pub duration: u32,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub key: String,
|
pub key: String,
|
||||||
|
pub publish_benchmarks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[my_codegen::post(
|
#[my_codegen::post(
|
||||||
@@ -139,6 +140,16 @@ pub mod runner {
|
|||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if payload.publish_benchmarks {
|
||||||
|
data.db
|
||||||
|
.analytics_create_psuedo_id_if_not_exists(&payload.key)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
data.db
|
||||||
|
.analytics_delete_all_records_for_campaign(&payload.key)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ pub async fn init_mcaptcha(data: &AppData, key: &str) -> ServiceResult<()> {
|
|||||||
|
|
||||||
for level in levels.iter() {
|
for level in levels.iter() {
|
||||||
let level = LevelBuilder::default()
|
let level = LevelBuilder::default()
|
||||||
.visitor_threshold(level.visitor_threshold as u32)
|
.visitor_threshold(level.visitor_threshold)
|
||||||
.difficulty_factor(level.difficulty_factor as u32)
|
.difficulty_factor(level.difficulty_factor)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -250,6 +250,7 @@ pub mod tests {
|
|||||||
levels: levels.into(),
|
levels: levels.into(),
|
||||||
duration: 30,
|
duration: 30,
|
||||||
description: "dummy".into(),
|
description: "dummy".into(),
|
||||||
|
publish_benchmarks: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. add level
|
// 1. add level
|
||||||
@@ -267,11 +268,11 @@ pub mod tests {
|
|||||||
key: token_key.key.clone(),
|
key: token_key.key.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = V1_API_ROUTES.pow.get_config;
|
let _url = V1_API_ROUTES.pow.get_config;
|
||||||
let mut prev = 0;
|
let mut prev = 0;
|
||||||
for (count, l) in levels.iter().enumerate() {
|
for (count, l) in levels.iter().enumerate() {
|
||||||
for l in prev..l.visitor_threshold * 2 {
|
for _l in prev..l.visitor_threshold * 2 {
|
||||||
let get_config_resp = test::call_service(
|
let _get_config_resp = test::call_service(
|
||||||
&app,
|
&app,
|
||||||
post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
|
post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
|
||||||
.to_request(),
|
.to_request(),
|
||||||
|
|||||||
@@ -32,6 +32,27 @@ pub struct ValidationToken {
|
|||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ApiWork {
|
||||||
|
pub string: String,
|
||||||
|
pub result: String,
|
||||||
|
pub nonce: u64,
|
||||||
|
pub key: String,
|
||||||
|
pub time: Option<u32>,
|
||||||
|
pub worker_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ApiWork> for Work {
|
||||||
|
fn from(value: ApiWork) -> Self {
|
||||||
|
Self {
|
||||||
|
string: value.string,
|
||||||
|
nonce: value.nonce,
|
||||||
|
result: value.result,
|
||||||
|
key: value.key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// API keys are mcaptcha actor names
|
// API keys are mcaptcha actor names
|
||||||
|
|
||||||
/// route handler that verifies PoW and issues a solution token
|
/// route handler that verifies PoW and issues a solution token
|
||||||
@@ -39,7 +60,7 @@ pub struct ValidationToken {
|
|||||||
#[my_codegen::post(path = "V1_API_ROUTES.pow.verify_pow()")]
|
#[my_codegen::post(path = "V1_API_ROUTES.pow.verify_pow()")]
|
||||||
pub async fn verify_pow(
|
pub async fn verify_pow(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
payload: web::Json<Work>,
|
payload: web::Json<ApiWork>,
|
||||||
data: AppData,
|
data: AppData,
|
||||||
) -> ServiceResult<impl Responder> {
|
) -> ServiceResult<impl Responder> {
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
@@ -52,8 +73,19 @@ pub async fn verify_pow(
|
|||||||
let ip = "127.0.1.1".into();
|
let ip = "127.0.1.1".into();
|
||||||
|
|
||||||
let key = payload.key.clone();
|
let key = payload.key.clone();
|
||||||
let res = data.captcha.verify_pow(payload.into_inner(), ip).await?;
|
let payload = payload.into_inner();
|
||||||
|
let worker_type = payload.worker_type.clone();
|
||||||
|
let time = payload.time;
|
||||||
|
let (res, difficulty_factor) = data.captcha.verify_pow(payload.into(), ip).await?;
|
||||||
data.stats.record_solve(&data, &key).await?;
|
data.stats.record_solve(&data, &key).await?;
|
||||||
|
if time.is_some() && worker_type.is_some() {
|
||||||
|
let analytics = db_core::CreatePerformanceAnalytics {
|
||||||
|
difficulty_factor,
|
||||||
|
time: time.unwrap(),
|
||||||
|
worker_type: worker_type.unwrap(),
|
||||||
|
};
|
||||||
|
data.db.analysis_save(&key, &analytics).await?;
|
||||||
|
}
|
||||||
let payload = ValidationToken { token: res };
|
let payload = ValidationToken { token: res };
|
||||||
Ok(HttpResponse::Ok().json(payload))
|
Ok(HttpResponse::Ok().json(payload))
|
||||||
}
|
}
|
||||||
@@ -81,6 +113,81 @@ pub mod tests {
|
|||||||
verify_pow_works(data).await;
|
verify_pow_works(data).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn verify_analytics_pow_works_pg() {
|
||||||
|
let data = crate::tests::pg::get_data().await;
|
||||||
|
verify_analytics_pow_works(data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn verify_analytics_pow_works_maria() {
|
||||||
|
let data = crate::tests::maria::get_data().await;
|
||||||
|
verify_analytics_pow_works(data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn verify_analytics_pow_works(data: ArcData) {
|
||||||
|
const NAME: &str = "powanalyticsuser";
|
||||||
|
const PASSWORD: &str = "testingpas";
|
||||||
|
const EMAIL: &str = "powanalyticsuser@a.com";
|
||||||
|
let data = &data;
|
||||||
|
|
||||||
|
delete_user(data, NAME).await;
|
||||||
|
|
||||||
|
register_and_signin(data, NAME, EMAIL, PASSWORD).await;
|
||||||
|
let (_, _signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
|
||||||
|
let app = get_app!(data).await;
|
||||||
|
|
||||||
|
let get_config_payload = GetConfigPayload {
|
||||||
|
key: token_key.key.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// update and check changes
|
||||||
|
|
||||||
|
let get_config_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(get_config_resp.status(), StatusCode::OK);
|
||||||
|
let config: PoWConfig = test::read_body_json(get_config_resp).await;
|
||||||
|
|
||||||
|
let pow = pow_sha256::ConfigBuilder::default()
|
||||||
|
.salt(config.salt)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let work = pow
|
||||||
|
.prove_work(&config.string.clone(), config.difficulty_factor)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let work = ApiWork {
|
||||||
|
string: config.string.clone(),
|
||||||
|
result: work.result,
|
||||||
|
nonce: work.nonce,
|
||||||
|
key: token_key.key.clone(),
|
||||||
|
time: Some(100),
|
||||||
|
worker_type: Some("wasm".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pow_verify_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(pow_verify_resp.status(), StatusCode::OK);
|
||||||
|
let limit = 50;
|
||||||
|
let offset = 0;
|
||||||
|
let mut analytics = data
|
||||||
|
.db
|
||||||
|
.analytics_fetch(&token_key.key, limit, offset)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(analytics.len(), 1);
|
||||||
|
let a = analytics.pop().unwrap();
|
||||||
|
assert_eq!(a.time, work.time.unwrap());
|
||||||
|
assert_eq!(a.worker_type, work.worker_type.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn verify_pow_works(data: ArcData) {
|
pub async fn verify_pow_works(data: ArcData) {
|
||||||
const NAME: &str = "powverifyusr";
|
const NAME: &str = "powverifyusr";
|
||||||
const PASSWORD: &str = "testingpas";
|
const PASSWORD: &str = "testingpas";
|
||||||
@@ -129,6 +236,12 @@ pub mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(pow_verify_resp.status(), StatusCode::OK);
|
assert_eq!(pow_verify_resp.status(), StatusCode::OK);
|
||||||
|
assert!(data
|
||||||
|
.db
|
||||||
|
.analytics_fetch(&token_key.key, 50, 0)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
let string_not_found = test::call_service(
|
let string_not_found = test::call_service(
|
||||||
&app,
|
&app,
|
||||||
|
|||||||
10
src/data.rs
10
src/data.rs
@@ -83,7 +83,11 @@ impl SystemGroup {
|
|||||||
enum_system_wrapper!(get_pow, String, CaptchaResult<Option<PoWConfig>>);
|
enum_system_wrapper!(get_pow, String, CaptchaResult<Option<PoWConfig>>);
|
||||||
|
|
||||||
// utility function to verify [Work]
|
// utility function to verify [Work]
|
||||||
pub async fn verify_pow(&self, msg: Work, ip: String) -> CaptchaResult<String> {
|
pub async fn verify_pow(
|
||||||
|
&self,
|
||||||
|
msg: Work,
|
||||||
|
ip: String,
|
||||||
|
) -> CaptchaResult<(String, u32)> {
|
||||||
match self {
|
match self {
|
||||||
Self::Embedded(val) => val.verify_pow(msg, ip).await,
|
Self::Embedded(val) => val.verify_pow(msg, ip).await,
|
||||||
Self::Redis(val) => val.verify_pow(msg, ip).await,
|
Self::Redis(val) => val.verify_pow(msg, ip).await,
|
||||||
@@ -203,9 +207,9 @@ impl Data {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let stats: Box<dyn Stats> = if s.captcha.enable_stats {
|
let stats: Box<dyn Stats> = if s.captcha.enable_stats {
|
||||||
Box::new(Real::default())
|
Box::<Real>::default()
|
||||||
} else {
|
} else {
|
||||||
Box::new(Dummy::default())
|
Box::<Dummy>::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = Data {
|
let data = Data {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ mod tests {
|
|||||||
let duration = Duration::from_secs(DURATION);
|
let duration = Duration::from_secs(DURATION);
|
||||||
|
|
||||||
// register works
|
// register works
|
||||||
let _ = DemoUser::register_demo_user(&data).await.unwrap();
|
DemoUser::register_demo_user(&data).await.unwrap();
|
||||||
let payload = AccountCheckPayload {
|
let payload = AccountCheckPayload {
|
||||||
val: DEMO_USER.into(),
|
val: DEMO_USER.into(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,15 +35,22 @@ struct AdvanceEditPage {
|
|||||||
name: String,
|
name: String,
|
||||||
key: String,
|
key: String,
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level>,
|
||||||
|
publish_benchmarks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AdvanceEditPage {
|
impl AdvanceEditPage {
|
||||||
fn new(config: Captcha, levels: Vec<Level>, key: String) -> Self {
|
fn new(
|
||||||
|
config: Captcha,
|
||||||
|
levels: Vec<Level>,
|
||||||
|
key: String,
|
||||||
|
publish_benchmarks: bool,
|
||||||
|
) -> Self {
|
||||||
AdvanceEditPage {
|
AdvanceEditPage {
|
||||||
duration: config.duration as u32,
|
duration: config.duration as u32,
|
||||||
name: config.description,
|
name: config.description,
|
||||||
levels,
|
levels,
|
||||||
key,
|
key,
|
||||||
|
publish_benchmarks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,8 +70,9 @@ pub async fn advance(
|
|||||||
|
|
||||||
let config = data.db.get_captcha_config(&username, &key).await?;
|
let config = data.db.get_captcha_config(&username, &key).await?;
|
||||||
let levels = data.db.get_captcha_levels(Some(&username), &key).await?;
|
let levels = data.db.get_captcha_levels(Some(&username), &key).await?;
|
||||||
|
let publish_benchmarks = data.db.analytics_captcha_is_published(&key).await?;
|
||||||
|
|
||||||
let body = AdvanceEditPage::new(config, levels, key)
|
let body = AdvanceEditPage::new(config, levels, key, publish_benchmarks)
|
||||||
.render_once()
|
.render_once()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
@@ -106,11 +114,14 @@ pub async fn easy(
|
|||||||
match data.db.get_traffic_pattern(&username, &key).await {
|
match data.db.get_traffic_pattern(&username, &key).await {
|
||||||
Ok(c) => {
|
Ok(c) => {
|
||||||
let config = data.db.get_captcha_config(&username, &key).await?;
|
let config = data.db.get_captcha_config(&username, &key).await?;
|
||||||
|
let publish_benchmarks =
|
||||||
|
data.db.analytics_captcha_is_published(&key).await?;
|
||||||
let pattern = TrafficPatternRequest {
|
let pattern = TrafficPatternRequest {
|
||||||
peak_sustainable_traffic: c.peak_sustainable_traffic as u32,
|
peak_sustainable_traffic: c.peak_sustainable_traffic,
|
||||||
avg_traffic: c.avg_traffic as u32,
|
avg_traffic: c.avg_traffic,
|
||||||
broke_my_site_traffic: c.broke_my_site_traffic.map(|n| n as u32),
|
broke_my_site_traffic: c.broke_my_site_traffic.map(|n| n),
|
||||||
description: config.description,
|
description: config.description,
|
||||||
|
publish_benchmarks,
|
||||||
};
|
};
|
||||||
|
|
||||||
let page = EasyEditPage::new(key, pattern).render_once().unwrap();
|
let page = EasyEditPage::new(key, pattern).render_once().unwrap();
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ struct IndexPage {
|
|||||||
key: String,
|
key: String,
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level>,
|
||||||
stats: CaptchaStats,
|
stats: CaptchaStats,
|
||||||
|
publish_benchmarks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexPage {
|
impl IndexPage {
|
||||||
@@ -44,6 +45,7 @@ impl IndexPage {
|
|||||||
config: Captcha,
|
config: Captcha,
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level>,
|
||||||
key: String,
|
key: String,
|
||||||
|
publish_benchmarks: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
IndexPage {
|
IndexPage {
|
||||||
duration: config.duration as u32,
|
duration: config.duration as u32,
|
||||||
@@ -51,6 +53,7 @@ impl IndexPage {
|
|||||||
levels,
|
levels,
|
||||||
key,
|
key,
|
||||||
stats,
|
stats,
|
||||||
|
publish_benchmarks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +73,9 @@ pub async fn view_sitekey(
|
|||||||
let config = data.db.get_captcha_config(&username, &key).await?;
|
let config = data.db.get_captcha_config(&username, &key).await?;
|
||||||
let levels = data.db.get_captcha_levels(Some(&username), &key).await?;
|
let levels = data.db.get_captcha_levels(Some(&username), &key).await?;
|
||||||
let stats = data.stats.fetch(&data, &username, &key).await?;
|
let stats = data.stats.fetch(&data, &username, &key).await?;
|
||||||
|
let publish_benchmarks = data.db.analytics_captcha_is_published(&key).await?;
|
||||||
|
|
||||||
let body = IndexPage::new(stats, config, levels, key)
|
let body = IndexPage::new(stats, config, levels, key, publish_benchmarks)
|
||||||
.render_once()
|
.render_once()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use std::env;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
use config::{Config, ConfigError, Environment, File};
|
use config::{Config, ConfigError, Environment, File};
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use log::{debug, warn};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -152,25 +152,34 @@ impl Settings {
|
|||||||
.expect("unable to set capatcha.enable_stats default config");
|
.expect("unable to set capatcha.enable_stats default config");
|
||||||
|
|
||||||
if let Ok(path) = env::var("MCAPTCHA_CONFIG") {
|
if let Ok(path) = env::var("MCAPTCHA_CONFIG") {
|
||||||
s.merge(File::with_name(&path))?;
|
let absolute_path = Path::new(&path).canonicalize().unwrap();
|
||||||
|
log::info!(
|
||||||
|
"Loading config file from {}",
|
||||||
|
absolute_path.to_str().unwrap()
|
||||||
|
);
|
||||||
|
s.merge(File::with_name(absolute_path.to_str().unwrap()))?;
|
||||||
} else if Path::new(CURRENT_DIR).exists() {
|
} else if Path::new(CURRENT_DIR).exists() {
|
||||||
|
let absolute_path = fs::canonicalize(CURRENT_DIR).unwrap();
|
||||||
|
log::info!(
|
||||||
|
"Loading config file from {}",
|
||||||
|
absolute_path.to_str().unwrap()
|
||||||
|
);
|
||||||
// merging default config from file
|
// merging default config from file
|
||||||
s.merge(File::with_name(CURRENT_DIR))?;
|
s.merge(File::with_name(absolute_path.to_str().unwrap()))?;
|
||||||
} else if Path::new(ETC).exists() {
|
} else if Path::new(ETC).exists() {
|
||||||
|
log::info!("{}", format!("Loading config file from {}", ETC));
|
||||||
s.merge(File::with_name(ETC))?;
|
s.merge(File::with_name(ETC))?;
|
||||||
} else {
|
} else {
|
||||||
log::warn!("configuration file not found");
|
log::warn!("Configuration file not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
s.merge(Environment::with_prefix("MCAPTCHA").separator("_"))?;
|
s.merge(Environment::with_prefix("MCAPTCHA").separator("_"))?;
|
||||||
|
|
||||||
check_url(&s);
|
check_url(&s);
|
||||||
|
|
||||||
match env::var("PORT") {
|
if let Ok(val) = env::var("PORT") {
|
||||||
Ok(val) => {
|
s.set("server.port", val).unwrap();
|
||||||
s.set("server.port", val).unwrap();
|
log::info!("Overriding [server].port with environment variable");
|
||||||
}
|
|
||||||
Err(e) => warn!("couldn't interpret PORT: {}", e),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match env::var("DATABASE_URL") {
|
match env::var("DATABASE_URL") {
|
||||||
@@ -180,8 +189,9 @@ impl Settings {
|
|||||||
let database_type = DBType::from_url(&url).unwrap();
|
let database_type = DBType::from_url(&url).unwrap();
|
||||||
s.set("database.database_type", database_type.to_string())
|
s.set("database.database_type", database_type.to_string())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
log::info!("Overriding [database].url and [database].database_type with environment variable");
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(_e) => {
|
||||||
set_database_url(&mut s);
|
set_database_url(&mut s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,8 +225,11 @@ fn set_database_url(s: &mut Config) {
|
|||||||
r"postgres://{}:{}@{}:{}/{}",
|
r"postgres://{}:{}@{}:{}/{}",
|
||||||
s.get::<String>("database.username")
|
s.get::<String>("database.username")
|
||||||
.expect("Couldn't access database username"),
|
.expect("Couldn't access database username"),
|
||||||
s.get::<String>("database.password")
|
urlencoding::encode(
|
||||||
.expect("Couldn't access database password"),
|
s.get::<String>("database.password")
|
||||||
|
.expect("Couldn't access database password")
|
||||||
|
.as_str()
|
||||||
|
),
|
||||||
s.get::<String>("database.hostname")
|
s.get::<String>("database.hostname")
|
||||||
.expect("Couldn't access database hostname"),
|
.expect("Couldn't access database hostname"),
|
||||||
s.get::<String>("database.port")
|
s.get::<String>("database.port")
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ pub mod pg {
|
|||||||
settings.captcha.runners = Some(1);
|
settings.captcha.runners = Some(1);
|
||||||
settings.database.url = url.clone();
|
settings.database.url = url.clone();
|
||||||
settings.database.database_type = DBType::Postgres;
|
settings.database.database_type = DBType::Postgres;
|
||||||
let data = Data::new(&settings).await;
|
|
||||||
data
|
Data::new(&settings).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub mod maria {
|
pub mod maria {
|
||||||
@@ -71,8 +71,8 @@ pub mod maria {
|
|||||||
settings.captcha.runners = Some(1);
|
settings.captcha.runners = Some(1);
|
||||||
settings.database.url = url.clone();
|
settings.database.url = url.clone();
|
||||||
settings.database.database_type = DBType::Maria;
|
settings.database.database_type = DBType::Maria;
|
||||||
let data = Data::new(&settings).await;
|
|
||||||
data
|
Data::new(&settings).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//pub async fn get_data() -> ArcData {
|
//pub async fn get_data() -> ArcData {
|
||||||
@@ -118,7 +118,7 @@ macro_rules! get_app {
|
|||||||
.wrap(actix_middleware::NormalizePath::new(
|
.wrap(actix_middleware::NormalizePath::new(
|
||||||
actix_middleware::TrailingSlash::Trim,
|
actix_middleware::TrailingSlash::Trim,
|
||||||
))
|
))
|
||||||
.configure(crate::routes::services),
|
.configure($crate::routes::services),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
($data:expr) => {
|
($data:expr) => {
|
||||||
@@ -262,5 +262,6 @@ pub fn get_level_data() -> CreateCaptcha {
|
|||||||
levels,
|
levels,
|
||||||
duration: 30,
|
duration: 30,
|
||||||
description: "dummy".into(),
|
description: "dummy".into(),
|
||||||
|
publish_benchmarks: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
* mCaptcha is a PoW based DoS protection software.
|
||||||
|
* This is the frontend web component of the mCaptcha system
|
||||||
|
* Copyright © 2023 Aravinth Manivnanan <realaravinth@batsense.net>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||||
* License, or (at your option) any later version.
|
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
@@ -8,6 +8,16 @@
|
|||||||
class="auth__logo" alt="mcaptcha logo" />
|
class="auth__logo" alt="mcaptcha logo" />
|
||||||
|
|
||||||
|
|
||||||
|
<. if !crate::SETTINGS.allow_registration { .>
|
||||||
|
<table class="reg-closed__table">
|
||||||
|
<thead class="reg-closed__table-heading">
|
||||||
|
<tr><th colspan="4" class="reg-closed__table-title-text">Registration closed</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="reg-closed__body">
|
||||||
|
<tr><td class="reg-closed__body-text">This mCaptcha instance is closed for registrations.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<. } else {.>
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
action="<.= crate::V1_API_ROUTES.auth.register .>"
|
action="<.= crate::V1_API_ROUTES.auth.register .>"
|
||||||
@@ -73,5 +83,6 @@
|
|||||||
<a href="<.= crate::PAGES.auth.login .>" class="auth__secondary-action__link">Log in</a>
|
<a href="<.= crate::PAGES.auth.login .>" class="auth__secondary-action__link">Log in</a>
|
||||||
</p>
|
</p>
|
||||||
<. include!("../demo-user-banner.html"); .>
|
<. include!("../demo-user-banner.html"); .>
|
||||||
|
<. } .>
|
||||||
</div>
|
</div>
|
||||||
<. include!("../../components/footers.html"); .>
|
<. include!("../../components/footers.html"); .>
|
||||||
|
|||||||
32
templates/auth/register/main.scss
Normal file
32
templates/auth/register/main.scss
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Gusted <postmaster@gusted.xyz>
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import '../../components/table/main';
|
||||||
|
|
||||||
|
.reg-closed__table {
|
||||||
|
@include table;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-closed__table-title-text {
|
||||||
|
@include table__title-text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reg-closed__body-text {
|
||||||
|
display: block;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
@@ -45,5 +45,16 @@
|
|||||||
<. } .>
|
<. } .>
|
||||||
<. } .>
|
<. } .>
|
||||||
|
|
||||||
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
|
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||||
|
<input
|
||||||
|
class="sitekey-form__input"
|
||||||
|
type="checkbox"
|
||||||
|
name="publish_benchmarks"
|
||||||
|
id="publish_benchmarks"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
<button class="sitekey-form__submit" type="submit">Submit</button>
|
<button class="sitekey-form__submit" type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ export const addSubmitEventListener = (): void =>
|
|||||||
const submit = async (e: Event) => {
|
const submit = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|
||||||
|
const PUBLISH_BENCHMARKS = <HTMLInputElement>(
|
||||||
|
FORM.querySelector("#publish_benchmarks")
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const description = validateDescription(e);
|
const description = validateDescription(e);
|
||||||
const duration = validateDuration();
|
const duration = validateDuration();
|
||||||
|
|
||||||
@@ -50,6 +57,7 @@ const submit = async (e: Event) => {
|
|||||||
levels: levels,
|
levels: levels,
|
||||||
duration,
|
duration,
|
||||||
description,
|
description,
|
||||||
|
publish_benchmarks: PUBLISH_BENCHMARKS.checked,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`);
|
console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
||||||
<label class="sitekey-form__label" for="avg_traffic">
|
<label class="sitekey-form__label" for="avg_traffic">
|
||||||
Average Traffic of your website
|
Average Traffic of your website
|
||||||
<input
|
<input
|
||||||
@@ -38,7 +39,6 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<label class="sitekey-form__label" for="avg_traffic">
|
<label class="sitekey-form__label" for="avg_traffic">
|
||||||
Maximum traffic that your website can handle
|
Maximum traffic that your website can handle
|
||||||
<input
|
<input
|
||||||
@@ -68,5 +68,17 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
|
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||||
|
<input
|
||||||
|
class="sitekey-form__input"
|
||||||
|
type="checkbox"
|
||||||
|
name="publish_benchmarks"
|
||||||
|
id="publish_benchmarks"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
<button class="sitekey-form__submit" type="submit">Submit</button>
|
<button class="sitekey-form__submit" type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ type TrafficPattern = {
|
|||||||
peak_sustainable_traffic: number;
|
peak_sustainable_traffic: number;
|
||||||
broke_my_site_traffic?: number;
|
broke_my_site_traffic?: number;
|
||||||
description: string;
|
description: string;
|
||||||
|
publish_benchmarks: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validate = (e: Event): TrafficPattern => {
|
export const validate = (e: Event): TrafficPattern => {
|
||||||
@@ -49,9 +50,7 @@ export const validate = (e: Event): TrafficPattern => {
|
|||||||
|
|
||||||
let broke_is_set = false;
|
let broke_is_set = false;
|
||||||
|
|
||||||
const AVG_TRAFFIC = <HTMLInputElement>(
|
const AVG_TRAFFIC = <HTMLInputElement>FORM.querySelector("#avg_traffic");
|
||||||
FORM.querySelector("#avg_traffic")
|
|
||||||
);
|
|
||||||
const PEAK_TRAFFIC = <HTMLInputElement>(
|
const PEAK_TRAFFIC = <HTMLInputElement>(
|
||||||
FORM.querySelector("#peak_sustainable_traffic")
|
FORM.querySelector("#peak_sustainable_traffic")
|
||||||
);
|
);
|
||||||
@@ -59,6 +58,10 @@ export const validate = (e: Event): TrafficPattern => {
|
|||||||
FORM.querySelector("#broke_my_site_traffic")
|
FORM.querySelector("#broke_my_site_traffic")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const PUBLISH_BENCHMARKS = <HTMLInputElement>(
|
||||||
|
FORM.querySelector("#publish_benchmarks")
|
||||||
|
);
|
||||||
|
|
||||||
isBlankString(AVG_TRAFFIC.value, avg_traffic_name);
|
isBlankString(AVG_TRAFFIC.value, avg_traffic_name);
|
||||||
isBlankString(PEAK_TRAFFIC.value, peak_traffic_name);
|
isBlankString(PEAK_TRAFFIC.value, peak_traffic_name);
|
||||||
|
|
||||||
@@ -101,6 +104,7 @@ export const validate = (e: Event): TrafficPattern => {
|
|||||||
peak_sustainable_traffic,
|
peak_sustainable_traffic,
|
||||||
broke_my_site_traffic,
|
broke_my_site_traffic,
|
||||||
description,
|
description,
|
||||||
|
publish_benchmarks: PUBLISH_BENCHMARKS.checked,
|
||||||
};
|
};
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
|
|||||||
@@ -16,6 +16,22 @@
|
|||||||
<. } .>
|
<. } .>
|
||||||
<. let level = levels.len() + 1; .>
|
<. let level = levels.len() + 1; .>
|
||||||
<. include!("../add/advance/add-level.html"); .>
|
<. include!("../add/advance/add-level.html"); .>
|
||||||
|
|
||||||
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
|
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||||
|
<input
|
||||||
|
class="sitekey-form__input"
|
||||||
|
type="checkbox"
|
||||||
|
id="publish_benchmarks"
|
||||||
|
name="publish_benchmarks"
|
||||||
|
<. if publish_benchmarks { .>
|
||||||
|
checked
|
||||||
|
<. }.>
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button data-sitekey="<.= key .>"
|
<button data-sitekey="<.= key .>"
|
||||||
id="sitekey-form__submit" class="sitekey-form__submit" type="submit">
|
id="sitekey-form__submit" class="sitekey-form__submit" type="submit">
|
||||||
Submit
|
Submit
|
||||||
|
|||||||
@@ -61,6 +61,21 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
|
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||||
|
<input
|
||||||
|
class="sitekey-form__input"
|
||||||
|
type="checkbox"
|
||||||
|
id="publish_benchmarks"
|
||||||
|
name="publish_benchmarks"
|
||||||
|
<. if pattern.publish_benchmarks { .>
|
||||||
|
checked
|
||||||
|
<. }.>
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<button data-sitekey="<.= key .>" class="sitekey-form__submit" type="submit">
|
<button data-sitekey="<.= key .>" class="sitekey-form__submit" type="submit">
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -47,11 +47,19 @@ const submit = async (e: Event) => {
|
|||||||
|
|
||||||
const key = BTN.get().dataset.sitekey;
|
const key = BTN.get().dataset.sitekey;
|
||||||
|
|
||||||
|
|
||||||
|
const PUBLISH_BENCHMARKS = <HTMLInputElement>(
|
||||||
|
Add.FORM.querySelector("#publish_benchmarks")
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
levels,
|
levels,
|
||||||
duration,
|
duration,
|
||||||
description,
|
description,
|
||||||
key,
|
key,
|
||||||
|
publish_benchmarks: PUBLISH_BENCHMARKS.checked,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`);
|
console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
<label class="sitekey-form__level-label" for="difficulty<.= num .>">
|
<label class="sitekey-form__level-label" for="difficulty<.= num .>">
|
||||||
Difficulty
|
Difficulty
|
||||||
<input
|
<input
|
||||||
readonly="readonly"
|
|
||||||
type="number"
|
type="number"
|
||||||
id="difficulty<.= num .>"
|
id="difficulty<.= num .>"
|
||||||
class="sitekey-form__level-input"
|
class="sitekey-form__level-input"
|
||||||
|
|||||||
@@ -23,6 +23,23 @@
|
|||||||
<. for (count, level) in levels.iter().enumerate() { .>
|
<. for (count, level) in levels.iter().enumerate() { .>
|
||||||
<. include!("./existing-level.html"); .>
|
<. include!("./existing-level.html"); .>
|
||||||
<. } .>
|
<. } .>
|
||||||
|
|
||||||
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
|
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
||||||
|
<input
|
||||||
|
class="sitekey-form__input"
|
||||||
|
type="checkbox"
|
||||||
|
id="publish_benchmarks"
|
||||||
|
readonly="readonly"
|
||||||
|
name="publish_benchmarks"
|
||||||
|
<. if publish_benchmarks { .>
|
||||||
|
checked
|
||||||
|
<. }.>
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<./* synchronise with "./__form-bottom.html" Lines below should break form */.>
|
<./* synchronise with "./__form-bottom.html" Lines below should break form */.>
|
||||||
</form>
|
</form>
|
||||||
<. include!("./stats.html"); .>
|
<. include!("./stats.html"); .>
|
||||||
|
|||||||
@@ -19,11 +19,12 @@
|
|||||||
<span id="widget__verification-text--before">I'm not a robot</span>
|
<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--during">Processing...</span>
|
||||||
<span id="widget__verification-text--after">Verified!</span>
|
<span id="widget__verification-text--after">Verified!</span>
|
||||||
<span id="widget__verification-text--error">Something wen't wrong</span>
|
<span id="widget__verification-text--error">Something went wrong</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="widget__mcaptcha-details">
|
<div class="widget__mcaptcha-details">
|
||||||
<a href="<.= crate::PKG_HOMEPAGE .>"
|
<a href="<.= crate::PKG_HOMEPAGE .>"
|
||||||
class="widget__mcaptcha-logo-container"
|
class="widget__mcaptcha-logo-container"
|
||||||
|
target="_blank"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="widget__mcaptcha-logo"
|
class="widget__mcaptcha-logo"
|
||||||
@@ -33,12 +34,14 @@
|
|||||||
<p class="widget__mcaptcha-brand-name">mCaptcha</p>
|
<p class="widget__mcaptcha-brand-name">mCaptcha</p>
|
||||||
</a>
|
</a>
|
||||||
<div class="widget__mcaptcha-info-container">
|
<div class="widget__mcaptcha-info-container">
|
||||||
<a class="widget__mcaptcha-info-link"
|
<a class="widget__mcaptcha-info-link"
|
||||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
|
target="_blank"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
|
||||||
Privacy
|
Privacy
|
||||||
</a>
|
</a>
|
||||||
<a class="widget__mcaptcha-info-link"
|
<a class="widget__mcaptcha-info-link"
|
||||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
|
target="_blank"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
|
||||||
Terms
|
Terms
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
|||||||
worker.onmessage = async (event: MessageEvent) => {
|
worker.onmessage = async (event: MessageEvent) => {
|
||||||
const resp: ServiceWorkerWork = event.data;
|
const resp: ServiceWorkerWork = event.data;
|
||||||
console.log(
|
console.log(
|
||||||
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.duration}`
|
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.work.time}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const proof: Work = {
|
const proof: Work = {
|
||||||
@@ -63,6 +63,8 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
|||||||
string: config.string,
|
string: config.string,
|
||||||
nonce: resp.work.nonce,
|
nonce: resp.work.nonce,
|
||||||
result: resp.work.result,
|
result: resp.work.result,
|
||||||
|
time: Math.trunc(resp.work.time),
|
||||||
|
worker_type: resp.work.worker_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. submit work
|
// 3. submit work
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
* mCaptcha is a PoW based DoS protection software.
|
||||||
|
* This is the frontend web component of the mCaptcha system
|
||||||
|
* Copyright © 2023 Aravinth Manivnanan <realaravinth@batsense.net>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||||
* License, or (at your option) any later version.
|
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import '../reset';
|
@import "../reset";
|
||||||
|
|
||||||
.widget__contaienr {
|
.widget__contaienr {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -11,31 +11,66 @@
|
|||||||
|
|
||||||
import { gen_pow } from "@mcaptcha/pow-wasm";
|
import { gen_pow } from "@mcaptcha/pow-wasm";
|
||||||
import * as p from "@mcaptcha/pow_sha256-polyfill";
|
import * as p from "@mcaptcha/pow_sha256-polyfill";
|
||||||
import { WasmWork, PoWConfig } from "./types";
|
import { WasmWork, PoWConfig, SubmitWork } from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* proove work
|
* proove work
|
||||||
* @param {PoWConfig} config - the proof-of-work configuration using which
|
* @param {PoWConfig} config - the proof-of-work configuration using which
|
||||||
* work needs to be computed
|
* work needs to be computed
|
||||||
* */
|
* */
|
||||||
const prove = async (config: PoWConfig): Promise<WasmWork> => {
|
const prove = async (config: PoWConfig): Promise<SubmitWork> => {
|
||||||
let proof: WasmWork = null;
|
const WASM = "wasm";
|
||||||
|
const JS = "js";
|
||||||
if (WasmSupported) {
|
if (WasmSupported) {
|
||||||
|
let proof: WasmWork = null;
|
||||||
|
let res: SubmitWork = null;
|
||||||
|
let time: number = null;
|
||||||
|
|
||||||
|
const t0 = performance.now();
|
||||||
const proofString = gen_pow(
|
const proofString = gen_pow(
|
||||||
config.salt,
|
config.salt,
|
||||||
config.string,
|
config.string,
|
||||||
config.difficulty_factor
|
config.difficulty_factor
|
||||||
);
|
);
|
||||||
|
const t1 = performance.now();
|
||||||
|
time = t1 - t0;
|
||||||
|
|
||||||
proof = JSON.parse(proofString);
|
proof = JSON.parse(proofString);
|
||||||
|
const worker_type = WASM;
|
||||||
|
res = {
|
||||||
|
result: proof.result,
|
||||||
|
nonce: proof.nonce,
|
||||||
|
worker_type,
|
||||||
|
time,
|
||||||
|
};
|
||||||
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("WASM unsupported, expect delay during proof generation");
|
console.log("WASM unsupported, expect delay during proof generation");
|
||||||
|
|
||||||
|
let proof: WasmWork = null;
|
||||||
|
let time: number = null;
|
||||||
|
let res: SubmitWork = null;
|
||||||
|
|
||||||
|
const t0 = performance.now();
|
||||||
|
|
||||||
proof = await p.generate_work(
|
proof = await p.generate_work(
|
||||||
config.salt,
|
config.salt,
|
||||||
config.string,
|
config.string,
|
||||||
config.difficulty_factor
|
config.difficulty_factor
|
||||||
);
|
);
|
||||||
|
const t1 = performance.now();
|
||||||
|
time = t1 - t0;
|
||||||
|
|
||||||
|
const worker_type = JS;
|
||||||
|
res = {
|
||||||
|
result: proof.result,
|
||||||
|
nonce: proof.nonce,
|
||||||
|
worker_type,
|
||||||
|
time,
|
||||||
|
};
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
return proof;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// credits: @jf-bastien on Stack Overflow
|
// credits: @jf-bastien on Stack Overflow
|
||||||
|
|||||||
@@ -1,37 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
* mCaptcha is a PoW based DoS protection software.
|
||||||
|
* This is the frontend web component of the mCaptcha system
|
||||||
|
* Copyright © 2023 Aravinth Manivnanan <realaravinth@batsense.net>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||||
* License, or (at your option) any later version.
|
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||||
*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import log from "../logger";
|
||||||
|
|
||||||
import prove from "./prove";
|
import prove from "./prove";
|
||||||
import { PoWConfig, ServiceWorkerWork } from "./types";
|
import { PoWConfig, ServiceWorkerWork } from "./types";
|
||||||
import log from "../logger";
|
|
||||||
|
|
||||||
log.log("worker registered");
|
log.log("worker registered");
|
||||||
onmessage = async (e) => {
|
onmessage = async (e) => {
|
||||||
console.debug("message received at worker");
|
console.debug("message received at worker");
|
||||||
const config: PoWConfig = e.data;
|
const config: PoWConfig = e.data;
|
||||||
|
|
||||||
const t0 = performance.now();
|
|
||||||
const work = await prove(config);
|
const work = await prove(config);
|
||||||
|
|
||||||
const t1 = performance.now();
|
|
||||||
const duration = t1 - t0;
|
|
||||||
|
|
||||||
const res: ServiceWorkerWork = {
|
const res: ServiceWorkerWork = {
|
||||||
work,
|
work,
|
||||||
duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
postMessage(res);
|
postMessage(res);
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ export type Work = {
|
|||||||
nonce: number;
|
nonce: number;
|
||||||
string: string;
|
string: string;
|
||||||
key: string;
|
key: string;
|
||||||
|
time: number;
|
||||||
|
worker_type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SubmitWork = {
|
||||||
|
time: number;
|
||||||
|
worker_type: string;
|
||||||
|
result: string;
|
||||||
|
nonce: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WasmWork = {
|
export type WasmWork = {
|
||||||
@@ -22,8 +31,7 @@ export type WasmWork = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ServiceWorkerWork = {
|
export type ServiceWorkerWork = {
|
||||||
work: WasmWork;
|
work: SubmitWork;
|
||||||
duration: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PoWConfig = {
|
export type PoWConfig = {
|
||||||
|
|||||||
Reference in New Issue
Block a user