Compare commits

...

86 Commits

Author SHA1 Message Date
Aravinth Manivannan
32762cf6b5 fix: linting 2023-07-02 22:04:42 +05:30
Aravinth Manivannan
43024f1940 fix: CI: update make command 2023-07-02 21:54:42 +05:30
Aravinth Manivannan
1e0aedad61 chore: linting 2023-07-02 21:51:24 +05:30
Aravinth Manivannan
1b05cdc391 fix: truncate pow compute time before submitting 2023-07-02 20:27:03 +05:30
Aravinth Manivannan
3e5936cb83 fix: CI: pin mariadb to version 10 2023-07-02 18:37:38 +05:30
Aravinth Manivannan
2ca49cffe4 feat: see publication status in sitekey view page 2023-06-30 16:50:04 +05:30
Aravinth Manivannan
468752f691 feat: advance siteey create now allows setting publication status 2023-06-30 16:49:35 +05:30
Aravinth Manivannan
22edb04ce2 feat: advance sitekey edit now allows modifying publication status 2023-06-30 16:48:53 +05:30
Aravinth Manivannan
6834e555d8 chore: use db util method to check publication status 2023-06-30 16:33:20 +05:30
Aravinth Manivannan
3c6b13f947 feat: utility method to check if captcha is published 2023-06-30 16:15:44 +05:30
Aravinth Manivannan
99db889867 chore: refactor test (coverage and frontend) subroutines in Makefile 2023-06-30 07:55:53 +05:30
Aravinth Manivannan
56dba7b77f feat: option to publish pow performance numbers. Can un/publish from edit page too. 2023-06-30 07:55:12 +05:30
Aravinth Manivannan
1c4ee5b622 feat: upload pow performance benchmarks 2023-06-30 07:54:54 +05:30
Aravinth Manivannan
d8a145cf87 chore: refactor test workflows in Makefile 2023-06-30 07:28:13 +05:30
Aravinth Manivannan
8ca48d85ff fix: worker_type should not be unique 2023-06-30 07:18:34 +05:30
Aravinth Manivannan
2b82af9a0c feat: update novice captcha creation form to include publish_benchmarks
preference
2023-06-30 03:20:57 +05:30
Aravinth Manivannan
2cf5e48d8e feat: log pow performance stats while pow verification 2023-06-30 03:19:38 +05:30
Aravinth Manivannan
679a35216c feat: delete analytics when webmaster updates publishing status 2023-06-30 03:16:43 +05:30
Aravinth Manivannan
68b59ade8c feat: add psuedo ID support to publish campaign IDs 2023-06-30 01:48:24 +05:30
Aravinth Manivannan
8af09939ff feat: pagination on performance logs fetches 2023-06-28 22:54:18 +05:30
Aravinth Manivannan
dc380adfcf chore: bump libmcaptcha 2023-06-28 22:49:08 +05:30
Aravinth Manivannan
f8e3b720de fix: ignore failures when creating dependency containers.
Failures happen when containers already exist, so ignoring is alright
2023-06-28 11:59:26 +05:30
Aravinth Manivannan
5ae3b1f26e feat: store pow performance statistics against statistics 2023-06-27 19:47:04 +05:30
Aravinth Manivannan
2a1bda653d feat: run migrations using sqlx also 2023-06-27 19:46:54 +05:30
Aravinth Manivannan
b0db04f26a feat: make: deploy dependencies 2023-06-27 15:14:08 +05:30
Aravinth Manivannan
78de0b266f Merge pull request #77 from mCaptcha/nighwatch
Integration testing using Nighwatch (firefox + chromium)
2023-05-25 21:23:37 +05:30
Aravinth Manivannan
6ede578ad5 Merge pull request #83 from Benjamin-Loison/master
Correct a typo and add necessary spaces in `README.md`
2023-05-25 21:08:10 +05:30
Aravinth Manivannan
efed5f5f93 Merge pull request #82 from Supernova3339/patch-1
🐛 Typo fix in Documentation
2023-05-25 21:05:08 +05:30
Benjamin Loison
4a6850631a Correct a typo and add necessary spaces in README.md 2023-05-23 15:08:55 +02:00
SuperDev
0adbb0aa2f Update CONFIGURATION.md
FIx typo in documentation
2023-05-18 12:44:39 -05:00
Aravinth Manivannan
8f3faaa279 Merge pull request #75 from WizardTales/licensefix
fix(license): accidential AGPL in MIT licensed files
2023-05-01 16:02:58 +05:30
Aravinth Manivannan
5324969bd2 feat: install selenium drivers 2023-04-30 23:46:58 +05:30
Aravinth Manivannan
43dab030df feat: run nightwatch integration tests on CI 2023-04-30 20:18:47 +05:30
Aravinth Manivannan
9cc667851c feat: run integration tests using nightwatch js 2023-04-30 20:17:51 +05:30
Tobias Gurtzick
9fc7c31083 fix(license): accidential AGPL in MIT licensed files
fixes #69

Signed-off-by: Tobias Gurtzick <magic@wizardtales.com>
2023-04-18 11:20:12 +02:00
Aravinth Manivannan
90e60b0486 Merge pull request #70 from mCaptcha/fix-53
fix: update libmcaptcha to use connection manager
2023-03-31 17:29:58 +05:30
Aravinth Manivannan
58f93cb602 fix: update libmcaptcha to use connection manager
closes: https://github.com/mCaptcha/mCaptcha/issues/53
2023-03-31 16:20:13 +05:30
Aravinth Manivannan
fae50b19f8 Merge pull request #67 from mCaptcha/dependabot/cargo/openssl-0.10.48
chore(deps): bump openssl from 0.10.41 to 0.10.48
2023-03-25 11:43:57 +05:30
dependabot[bot]
e890ba0f57 chore(deps): bump openssl from 0.10.41 to 0.10.48
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.41 to 0.10.48.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.41...openssl-v0.10.48)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-25 05:03:34 +00:00
Aravinth Manivannan
744d94cf8d Merge pull request #66 from mCaptcha/dependabot/npm_and_yarn/docs/openapi/minimist-1.2.8
chore(deps): bump minimist from 1.2.5 to 1.2.8 in /docs/openapi
2023-03-25 10:33:03 +05:30
Aravinth Manivannan
31d12206aa feat: add NLnet funding details 2023-03-08 17:08:39 +05:30
dependabot[bot]
7764eda05d chore(deps): bump minimist from 1.2.5 to 1.2.8 in /docs/openapi
Bumps [minimist](https://github.com/minimistjs/minimist) from 1.2.5 to 1.2.8.
- [Release notes](https://github.com/minimistjs/minimist/releases)
- [Changelog](https://github.com/minimistjs/minimist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.8)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 12:38:56 +00:00
Aravinth Manivannan
f78669955c Merge pull request #60 from mCaptcha/fix-59
fix: copy OpenAPI build from build container to final container
2023-01-17 04:26:20 +05:30
Aravinth Manivannan
cadc15a7a1 fix: copy OpenAPI build from build container to final container
fixes: https://github.com/mCaptcha/mCaptcha/issues/59
2023-01-17 03:54:54 +05:30
Aravinth Manivannan
c1f6ce3ae2 Merge pull request #52 from Gusted/close-register-page
Hide register page when registrations are closed
2022-11-07 13:39:34 +05:30
Gusted
864549cb4c Fix copyright 2022-11-06 19:42:22 +01:00
Gusted
5713d4b1ae Hide register page when registrations are closed
- Improve UX for "private" instances.
2022-10-24 22:47:07 +02:00
Aravinth Manivannan
ac502b7c08 Merge pull request #51 from Gusted/fix-compiling
Fix compiling
2022-10-24 21:22:34 +05:30
Gusted
8dc690ca01 Remove redunant format 2022-10-24 16:22:10 +02:00
Gusted
a4f9c92b32 Update cargo 2022-10-22 21:53:18 +02:00
Gusted
8826f6df8f Fix compiling
- I made some mistakes while checking my two previous PRs(Seems like I
don't understand Rust that great after all).
- Do the url encoding only on the password part, not only the whole URL.
- Fix `temporary value dropped while borrowed` compile error.
2022-10-22 21:28:58 +02:00
Aravinth Manivannan
af35fdb48e Merge pull request #46 from Gusted/relax-port-env
Allow `PORT` environment to be not set
2022-10-18 15:51:22 +05:30
Gusted
9fd8ffd666 Add more logging for configuration/env 2022-10-17 19:49:42 +02:00
Aravinth Manivannan
521abd82d6 Merge pull request #47 from Gusted/encode-url-postgres
Encode connection URL to database
2022-10-17 15:42:02 +05:30
Gusted
021f2fe5b4 Encode connection URL to database
- If you have a database password that contains characters like `#` or `*`, sqlx
will error about a InvalidPort, this is due to not encoding the url.
[See issue on sqlx](https://github.com/launchbadge/sqlx/issues/1624).
- Removed useless statements.
2022-10-16 23:37:24 +02:00
Gusted
b3e0ff6769 Allow PORT environment to be not set
- It's quite weird to require the `PORT` environment to be set, when it
already can be set via the config file.
2022-10-16 23:07:10 +02:00
Aravinth Manivannan
97abca2520 Merge pull request #45 from DarianAnjuhal/master
feat: open mail link with target=_blank
2022-10-12 15:28:13 +05:30
Daniel Antlinger
96a6c98c10 feat: open mail link with target=_blank 2022-10-12 11:50:55 +02:00
realaravinth
9d285573d7 fix: typo in widget 2022-09-26 16:40:40 +05:30
Aravinth Manivannan
d506d291c3 Merge pull request #43 from DarianAnjuhal/master
feat: open links from widget in new pages target=_blank
2022-09-07 23:31:56 +05:30
Daniel Antlinger
223e8fb8c2 feat: open links from widget in new pages target=_blank 2022-09-07 14:05:25 +02:00
realaravinth
2abf57d16b fix: set correct upload dir 2022-08-15 18:01:41 +05:30
realaravinth
b3ee57d042 fix: configure gpg key 2022-08-15 17:37:48 +05:30
realaravinth
8c65edd257 fix: upload path 2022-08-15 17:25:22 +05:30
realaravinth
9f521fe199 feat: publish mcaptcha bin to dl.mcaptcha.org 2022-08-15 17:03:46 +05:30
realaravinth
8ac1e2b81e feat: package and sign dist assets 2022-08-15 17:03:46 +05:30
Aravinth Manivannan
5db58d477b Merge pull request #24 from mCaptcha/dependabot/npm_and_yarn/nanoid-3.3.4
Bump nanoid from 3.1.29 to 3.3.4
2022-08-14 01:32:55 +05:30
Aravinth Manivannan
db03cd3b1f Merge pull request #34 from mCaptcha/dependabot/npm_and_yarn/terser-5.14.2
chore(deps): bump terser from 5.9.0 to 5.14.2
2022-08-14 01:32:25 +05:30
realaravinth
e5e89bd8a0 chore: bump libmcaptcha, switch to master 2022-08-13 01:58:40 +05:30
Aravinth Manivannan
2dd6f063c5 Merge pull request #40 from Gusted/automate-releases
Automate releases via Github Actions
2022-08-12 18:39:48 +05:30
Gusted
bb81e7fb9b Automate releases via Github Actions
- Use Github Actions to compile static linked binaries for the three
major OS and upload them to Github Releases.
2022-08-12 14:55:57 +02:00
realaravinth
b3d00c89a6 feat: increase demo test waint time 2022-08-09 17:08:26 +05:30
realaravinth
8c9587ad65 feat: set custom runers and queue length 2022-08-09 16:30:04 +05:30
realaravinth
21825582e5 feat: fallabck to localhost when running tests 2022-08-09 16:29:24 +05:30
realaravinth
f8e6bdf229 feat: bump libmcaptcha 2022-08-09 16:29:05 +05:30
realaravinth
8c576d2b07 feat: queue length and IP runner config 2022-08-09 16:28:30 +05:30
realaravinth
c377cf431e feat & fix: ip queues 2022-08-09 02:52:17 +05:30
realaravinth
ce1b3b0856 feat: bump libmcaptcha and pow_sha256 2022-08-09 02:51:40 +05:30
Aravinth Manivannan
cd6cecfe4a Merge pull request #39 from evilsocket/master
fix: fixes broken docker-compose.yml (#38)
2022-08-05 17:40:05 +05:30
Simone Margaritelli
a66d75c3c3 fix: fixes broken docker-compose.yml (#38) 2022-08-05 14:00:40 +02:00
Aravinth Manivannan
3d9056e968 Merge pull request #36 from kianmeng/fix-typos
Fix typos
2022-08-05 14:26:11 +05:30
Kian-Meng Ang
bb42841a66 Fix typos 2022-08-04 23:06:54 +08:00
Aravinth Manivannan
961bb6c5f4 Merge pull request #35 from Gusted/use-full-screen
Let widget use full-screen
2022-07-27 21:35:52 +05:30
Gusted
f56bc6d9e4 Let widget use full-screen
- Instead of using static values for dimensions. Use responsive CSS and
take up the whole screen. Let the user deal with setting the correct
dimensions accordingly.
2022-07-26 22:58:17 +02:00
dependabot[bot]
8a667ad71f chore(deps): bump terser from 5.9.0 to 5.14.2
Bumps [terser](https://github.com/terser/terser) from 5.9.0 to 5.14.2.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-20 06:01:46 +00:00
dependabot[bot]
72cff2a470 Bump nanoid from 3.1.29 to 3.3.4
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.29 to 3.3.4.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.29...3.3.4)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-07 10:54:21 +00:00
70 changed files with 2201 additions and 416 deletions

View File

@@ -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"
@@ -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

View File

@@ -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"
@@ -81,6 +81,12 @@ jobs:
target target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: configure GPG key
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
env:
RELEASE_BOT_GPG_SIGNING_KEY: ${{ secrets.RELEASE_BOT_GPG_SIGNING_KEY }}
- name: load env - name: load env
run: | run: |
source .env_sample \ source .env_sample \
@@ -98,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:
@@ -122,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
@@ -133,6 +145,13 @@ jobs:
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'
run: make docker-publish run: make docker-publish
- name: publish bins
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
run: ./scripts/publish.sh publish master latest $DUMBSERVE_PASSWORD
env:
DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
- name: generate documentation - name: generate documentation
if: matrix.version == 'stable' && (github.repository == 'mCaptcha/mCaptcha') if: matrix.version == 'stable' && (github.repository == 'mCaptcha/mCaptcha')
run: make doc run: make doc

32
.github/workflows/tagged-release.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Create binary for release
# Only on tags that start with a "v"
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-pc-windows-gnu
archive: zip
- target: x86_64-unknown-linux-musl
archive: tar.gz tar.xz
- target: x86_64-apple-darwin
archive: zip
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Compile and release
uses: rust-build/rust-build.action@v1.3.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
RUSTTARGET: ${{ matrix.target }}
ARCHIVE_TYPES: ${{ matrix.archive }}

432
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,8 @@ log = "0.4"
lazy_static = "1.4" lazy_static = "1.4"
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] } 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 = { path = "../libmcaptcha", features = ["full"]} #libmcaptcha = { path = "../libmcaptcha", features = ["full"]}
rand = "0.8" rand = "0.8"
@@ -68,6 +69,8 @@ sailfish = "0.4.0"
mime = "0.3.16" mime = "0.3.16"
num_cpus = "1.13.1"
lettre = { version = "0.10.0-rc.3", features = [ lettre = { version = "0.10.0-rc.3", features = [
"builder", "builder",
"tokio1", "tokio1",
@@ -75,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]
@@ -101,7 +105,7 @@ serde_json = "1"
sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline", "mysql" ] } sqlx = { version = "0.5.13", features = [ "runtime-actix-rustls", "postgres", "time", "offline", "mysql" ] }
[dev-dependencies] [dev-dependencies]
pow_sha256 = { version = "0.2.1", git = "https://github.com/mcaptcha/pow_sha256" } pow_sha256 = { version = "0.3.1", git = "https://github.com/mcaptcha/pow_sha256", tag="0.3.1" }
awc = "3.0.0" awc = "3.0.0"

View File

@@ -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
View File

@@ -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}'

View File

@@ -34,7 +34,7 @@ yourself!](https://demo.mcaptcha.org/widget/?sitekey=pHy0AktWyOKuxZDzFfoaewncWec
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.
@@ -112,7 +112,6 @@ After the containers are up, visit [http://localhost:7000](http://localhost:7000
- 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) detailed alternate deployment
@@ -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.

View File

@@ -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();

View File

@@ -11,7 +11,7 @@ cookie_secret = "Zae0OOxf^bOJ#zN^&k7VozgW&QAx%n02TQFXpRMG4cCU0xMzgu3dna@tQ9dvc&T
# The port at which you want authentication to listen to # The port at which you want authentication to listen to
# takes a number, choose from 1000-10000 if you dont know what you are doing # takes a number, choose from 1000-10000 if you dont know what you are doing
port = 7000 port = 7000
#IP address. Enter 0.0.0.0 to listen on all availale addresses #IP address. Enter 0.0.0.0 to listen on all available addresses
ip= "0.0.0.0" ip= "0.0.0.0"
# enter your hostname, eg: example.com # enter your hostname, eg: example.com
domain = "localhost" domain = "localhost"
@@ -28,6 +28,8 @@ salt = "asdl;kjfhjawehfpa;osdkjasdvjaksndfpoanjdfainsdfaijdsfajlkjdsaf;ajsdfwero
# garbage collection period to manage mCaptcha system # garbage collection period to manage mCaptcha system
# leave untouched if you don't know what you are doing # leave untouched if you don't know what you are doing
gc = 30 gc = 30
runners = 4
queue_length = 2000
enable_stats = true enable_stats = true
[captcha.default_difficulty_strategy] [captcha.default_difficulty_strategy]

View File

@@ -13,7 +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 = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["minimal"], default-features = false } 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"] }
[features] [features]
default = [] default = []

View File

@@ -26,7 +26,7 @@
//! //!
//! ## Organisation //! ## Organisation
//! //!
//! Database functionallity is divided accross various modules: //! Database functionality is divided across various modules:
//! //!
//! - [errors](crate::auth): error data structures used in this crate //! - [errors](crate::auth): error data structures used in this crate
//! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting //! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting
@@ -242,14 +242,89 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
/// record PoWConfig confirms /// record PoWConfig confirms
async fn record_confirm(&self, key: &str) -> DBResult<()>; async fn record_confirm(&self, key: &str) -> DBResult<()>;
/// featch PoWConfig fetches /// fetch PoWConfig fetches
async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>>; async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>>;
/// featch PoWConfig solves /// fetch PoWConfig solves
async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>>; async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>>;
/// featch 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)]
@@ -287,7 +362,7 @@ pub struct AddNotification<'a> {
pub from: &'a str, pub from: &'a str,
/// heading of the notification /// heading of the notification
pub heading: &'a str, pub heading: &'a str,
/// mesage of the notification /// message of the notification
pub message: &'a str, pub message: &'a str,
} }
@@ -298,12 +373,12 @@ pub struct TrafficPattern {
pub avg_traffic: u32, pub avg_traffic: u32,
/// the peak traffic that the user's website can handle /// the peak traffic that the user's website can handle
pub peak_sustainable_traffic: u32, pub peak_sustainable_traffic: u32,
/// trafic that bought the user's website down; optional /// traffic that bought the user's website down; optional
pub broke_my_site_traffic: Option<u32>, pub broke_my_site_traffic: Option<u32>,
} }
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
/// data requried to create new captcha /// data required to create new captcha
pub struct CreateCaptcha<'a> { pub struct CreateCaptcha<'a> {
/// cool down duration /// cool down duration
pub duration: i32, pub duration: i32,
@@ -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

View File

@@ -30,7 +30,7 @@ pub trait GetConnection {
async fn get_conn(&self) -> DBResult<Self::Conn>; async fn get_conn(&self) -> DBResult<Self::Conn>;
} }
/// Create databse connection /// Create database connection
#[async_trait] #[async_trait]
pub trait Connect { pub trait Connect {
/// database specific pool-type /// database specific pool-type

View File

@@ -33,7 +33,7 @@ pub async fn database_works<'a, T: MCDatabase>(
db.delete_user(p.username).await.unwrap(); db.delete_user(p.username).await.unwrap();
assert!( assert!(
!db.username_exists(p.username).await.unwrap(), !db.username_exists(p.username).await.unwrap(),
"user is deleted so username shouldn't exsit" "user is deleted so username shouldn't exist"
); );
} }
@@ -89,11 +89,11 @@ pub async fn database_works<'a, T: MCDatabase>(
// testing email exists // testing email exists
assert!( assert!(
db.email_exists(p.email.as_ref().unwrap()).await.unwrap(), db.email_exists(p.email.as_ref().unwrap()).await.unwrap(),
"user is registered so email should exsit" "user is registered so email should exist"
); );
assert!( assert!(
db.username_exists(p.username).await.unwrap(), db.username_exists(p.username).await.unwrap(),
"user is registered so username should exsit" "user is registered so username should exist"
); );
// update password test. setting password = username // update password test. setting password = username
@@ -124,7 +124,7 @@ pub async fn database_works<'a, T: MCDatabase>(
db.delete_user(p.email.as_ref().unwrap()).await.unwrap(); db.delete_user(p.email.as_ref().unwrap()).await.unwrap();
assert!( assert!(
!db.username_exists(p.email.as_ref().unwrap()).await.unwrap(), !db.username_exists(p.email.as_ref().unwrap()).await.unwrap(),
"user is deleted so username shouldn't exsit" "user is deleted so username shouldn't exist"
); );
// register with email = None // register with email = None
@@ -133,11 +133,11 @@ pub async fn database_works<'a, T: MCDatabase>(
db.register(&p2).await.unwrap(); db.register(&p2).await.unwrap();
assert!( assert!(
db.username_exists(p2.username).await.unwrap(), db.username_exists(p2.username).await.unwrap(),
"user is registered so username should exsit" "user is registered so username should exist"
); );
assert!( assert!(
!db.email_exists(p.email.as_ref().unwrap()).await.unwrap(), !db.email_exists(p.email.as_ref().unwrap()).await.unwrap(),
"user registration with email is deleted; so email shouldn't exsit" "user registration with email is deleted; so email shouldn't exist"
); );
// testing get_email = None // testing get_email = None
@@ -155,7 +155,7 @@ pub async fn database_works<'a, T: MCDatabase>(
); );
assert!( assert!(
db.email_exists(p.email.as_ref().unwrap()).await.unwrap(), db.email_exists(p.email.as_ref().unwrap()).await.unwrap(),
"user was with empty email but email is set; so email should exsit" "user was with empty email but email is set; so email should exist"
); );
/* /*
@@ -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)

View File

@@ -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"

View File

@@ -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
);

View File

@@ -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
);

View File

@@ -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": [],

View File

@@ -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)]
@@ -35,7 +36,7 @@ pub struct Database {
/// Use an existing database pool /// Use an existing database pool
pub struct Conn(pub MySqlPool); pub struct Conn(pub MySqlPool);
/// Connect to databse /// Connect to database
pub enum ConnectionOptions { pub enum ConnectionOptions {
/// fresh connection /// fresh connection
Fresh(Fresh), Fresh(Fresh),
@@ -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)
@@ -824,7 +822,7 @@ impl MCDatabase for Database {
Ok(()) Ok(())
} }
/// featch PoWConfig fetches /// fetch PoWConfig fetches
async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>> { async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
let records = sqlx::query_as!( let records = sqlx::query_as!(
Date, Date,
@@ -850,7 +848,7 @@ impl MCDatabase for Database {
Ok(Date::dates_to_unix(records)) Ok(Date::dates_to_unix(records))
} }
/// featch PoWConfig solves /// fetch PoWConfig solves
async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>> { async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
let records = sqlx::query_as!( let records = sqlx::query_as!(
Date, Date,
@@ -874,7 +872,7 @@ impl MCDatabase for Database {
Ok(Date::dates_to_unix(records)) Ok(Date::dates_to_unix(records))
} }
/// featch 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>> {
let records = sqlx::query_as!( let records = sqlx::query_as!(
Date, Date,
@@ -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)]

View File

@@ -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"

View File

@@ -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
);

View File

@@ -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
);

View File

@@ -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": [

View File

@@ -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)]
@@ -35,7 +36,7 @@ pub struct Database {
/// Use an existing database pool /// Use an existing database pool
pub struct Conn(pub PgPool); pub struct Conn(pub PgPool);
/// Connect to databse /// Connect to database
pub enum ConnectionOptions { pub enum ConnectionOptions {
/// fresh connection /// fresh connection
Fresh(Fresh), Fresh(Fresh),
@@ -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)
@@ -830,7 +828,7 @@ impl MCDatabase for Database {
Ok(()) Ok(())
} }
/// featch PoWConfig fetches /// fetch PoWConfig fetches
async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>> { async fn fetch_config_fetched(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
let records = sqlx::query_as!( let records = sqlx::query_as!(
Date, Date,
@@ -856,7 +854,7 @@ impl MCDatabase for Database {
Ok(Date::dates_to_unix(records)) Ok(Date::dates_to_unix(records))
} }
/// featch PoWConfig solves /// fetch PoWConfig solves
async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>> { async fn fetch_solve(&self, user: &str, key: &str) -> DBResult<Vec<i64>> {
let records = sqlx::query_as!( let records = sqlx::query_as!(
Date, Date,
@@ -880,7 +878,7 @@ impl MCDatabase for Database {
Ok(Date::dates_to_unix(records)) Ok(Date::dates_to_unix(records))
} }
/// featch 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>> {
let records = sqlx::query_as!( let records = sqlx::query_as!(
Date, Date,
@@ -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)]

View File

@@ -7,12 +7,12 @@ services:
- 7000:7000 - 7000:7000
environment: environment:
DATABASE_URL: postgres://postgres:password@mcaptcha_postgres:5432/postgres # set password at placeholder DATABASE_URL: postgres://postgres:password@mcaptcha_postgres:5432/postgres # set password at placeholder
MCAPTCHA_REDIS_URL: redis://mcaptcha-redis/ MCAPTCHA_REDIS_URL: redis://mcaptcha_redis/
RUST_LOG: debug RUST_LOG: debug
PORT: 7000 PORT: 7000
depends_on: depends_on:
- mcaptcha-postgres - mcaptcha_postgres
- mcaptcha-redis - mcaptcha_redis
mcaptcha_postgres: mcaptcha_postgres:
image: postgres:13.2 image: postgres:13.2
@@ -22,7 +22,7 @@ services:
POSTGRES_PASSWORD: password # change password POSTGRES_PASSWORD: password # change password
PGDATA: /var/lib/postgresql/data/mcaptcha/ PGDATA: /var/lib/postgresql/data/mcaptcha/
mcaptcha-redis: mcaptcha_redis:
image: mcaptcha/cache:latest image: mcaptcha/cache:latest
volumes: volumes:

View File

@@ -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

View File

@@ -107,7 +107,7 @@ I use mariadb also in Docker
2. Create create database user: 2. Create create database user:
```bash ```bash
docker create docker create \
-p 3306:3306 \ -p 3306:3306 \
--name some-mariadb \ --name some-mariadb \
--env MARIADB_USER=maria \ --env MARIADB_USER=maria \

View File

@@ -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
View 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

39
scripts/integration.sh Executable file
View 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.chrome
xvfb-run --auto-servernum npm run test.firefox
else
yarn install
npx nightwatch ./test/mCaptcha.ts
fi
cd $PROJECT_ROOT
docker-compose down -v --remove-orphans || true

121
scripts/publish.sh Executable file
View File

@@ -0,0 +1,121 @@
#!/bin/bash
# Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# publish.sh: grab bin from docker container, pack, sign and upload
# $2: binary version
# $3: Docker img tag
# $4: dumbserve password
set -xEeuo pipefail
DUMBSERVE_USERNAME=mcaptcha
DUMBSERVE_PASSWORD=$4
DUMBSERVE_HOST="https://$DUMBSERVE_USERNAME:$DUMBSERVE_PASSWORD@dl.mcaptcha.org"
NAME=mcaptcha
KEY=0CBABF3084E84E867A76709750BE39D10ECE01FB
TMP_DIR=$(mktemp -d)
FILENAME="$NAME-$2-linux-amd64"
TARBALL=$FILENAME.tar.gz
TARGET_DIR="$TMP_DIR/$FILENAME/"
mkdir -p $TARGET_DIR
DOCKER_IMG="mcaptcha/$NAME:$3"
get_bin(){
echo "[*] Grabbing binary"
container_id=$(docker create $DOCKER_IMG)
docker cp $container_id:/usr/local/bin/$NAME $TARGET_DIR/
docker rm -v $container_id
}
copy() {
echo "[*] Copying dist assets"
cp README.md $TARGET_DIR
cp LICENSE.md $TARGET_DIR
cp CHANGELOG.md $TARGET_DIR
cp docker-compose.yml $TARGET_DIR
mkdir $TARGET_DIR/docs
cp docs/DEPLOYMENT.md $TARGET_DIR/docs
cp docs/CONFIGURATION.md $TARGET_DIR/docs
get_bin
}
pack() {
echo "[*] Creating dist tarball"
pushd $TMP_DIR
tar -cvzf $TARBALL $FILENAME
popd
}
checksum() {
echo "[*] Generating dist tarball checksum"
pushd $TMP_DIR
sha256sum $TARBALL > $TARBALL.sha256
popd
}
sign() {
echo "[*] Signing dist tarball checksum"
pushd $TMP_DIR
export GPG_TTY=$(tty)
gpg --verbose \
--pinentry-mode loopback \
--batch --yes \
--passphrase $GPG_PASSWORD \
--local-user $KEY \
--output $TARBALL.asc \
--sign --detach \
--armor $TARBALL
popd
}
delete_dir() {
curl --location --request DELETE "$DUMBSERVE_HOST/api/v1/files/delete" \
--header 'Content-Type: application/json' \
--data-raw "{
\"path\": \"$1\"
}"
}
upload_dist() {
upload_dir="mCaptcha/$1"
delete_dir $upload_dir
pushd $TMP_DIR
for file in $TARBALL $TARBALL.asc $TARBALL.sha256
do
curl -v \
-F upload=@$file \
"$DUMBSERVE_HOST/api/v1/files/upload?path=$upload_dir"
done
popd
}
publish() {
copy
pack
checksum
sign
upload_dist $2
}
$1 $@

View File

@@ -51,7 +51,7 @@ pub async fn uname_email_exists_works(data: ArcData) {
let cookies = get_cookie!(signin_resp); let cookies = get_cookie!(signin_resp);
let app = get_app!(data).await; let app = get_app!(data).await;
// chech if get user secret works // check if get user secret works
let resp = test::call_service( let resp = test::call_service(
&app, &app,
test::TestRequest::get() test::TestRequest::get()
@@ -62,7 +62,7 @@ pub async fn uname_email_exists_works(data: ArcData) {
.await; .await;
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
// chech if get user secret works // check if get user secret works
let resp = test::call_service( let resp = test::call_service(
&app, &app,
test::TestRequest::post() test::TestRequest::post()
@@ -178,7 +178,7 @@ pub async fn email_udpate_password_validation_del_userworks(data: ArcData) {
) )
.await; .await;
// wrong password while deleteing account // wrong password while deleting account
let mut payload = Password { let mut payload = Password {
password: NAME.into(), password: NAME.into(),
}; };

View File

@@ -86,7 +86,7 @@ pub mod runners {
pub password: String, pub password: String,
} }
/// returns Ok(()) when everything checks out and the user is authenticated. Erros otherwise /// returns Ok(()) when everything checks out and the user is authenticated. Errors otherwise
pub async fn login_runner(payload: Login, data: &AppData) -> ServiceResult<String> { pub async fn login_runner(payload: Login, data: &AppData) -> ServiceResult<String> {
use argon2_creds::Config; use argon2_creds::Config;

View File

@@ -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))
} }

View File

@@ -56,10 +56,13 @@ pub struct TrafficPatternRequest {
pub avg_traffic: u32, pub avg_traffic: u32,
/// the peak traffic that the user's website can handle /// the peak traffic that the user's website can handle
pub peak_sustainable_traffic: u32, pub peak_sustainable_traffic: u32,
/// trafic that bought the user's website down; optional /// traffic that bought the user's website down; optional
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());
} }
} }

View File

@@ -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(

View File

@@ -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(())
} }
} }
@@ -191,7 +202,7 @@ mod tests {
let updated_token: MCaptchaDetails = let updated_token: MCaptchaDetails =
test::read_body_json(update_token_resp).await; test::read_body_json(update_token_resp).await;
// get levels with udpated key // get levels with updated key
let get_token_resp = test::call_service( let get_token_resp = test::call_service(
&app, &app,
post_request!(&updated_token, ROUTES.captcha.get) post_request!(&updated_token, ROUTES.captcha.get)
@@ -199,7 +210,7 @@ mod tests {
.to_request(), .to_request(),
) )
.await; .await;
// if updated key doesn't exist in databse, a non 200 result will bereturned // if updated key doesn't exist in database, a non 200 result will bereturned
assert_eq!(get_token_resp.status(), StatusCode::OK); assert_eq!(get_token_resp.status(), StatusCode::OK);
// get stats // get stats
@@ -211,7 +222,7 @@ mod tests {
.to_request(), .to_request(),
) )
.await; .await;
// if updated key doesn't exist in databse, a non 200 result will bereturned // if updated key doesn't exist in database, a non 200 result will bereturned
assert_eq!(get_statis_resp.status(), StatusCode::OK); assert_eq!(get_statis_resp.status(), StatusCode::OK);
} }
} }

View File

@@ -46,7 +46,7 @@ pub mod routes {
} }
} }
/// emmits build details of the bninary /// emits build details of the bninary
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")] #[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")]
async fn build_details() -> impl Responder { async fn build_details() -> impl Responder {
let build = BuildDetails { let build = BuildDetails {

View File

@@ -42,7 +42,7 @@ pub async fn add_notification(
id: Identity, id: Identity,
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
let sender = id.identity().unwrap(); let sender = id.identity().unwrap();
// TODO handle error where payload.to doesnt exist // TODO handle error where payload.to doesn't exist
let p = AddNotification { let p = AddNotification {
from: &sender, from: &sender,
@@ -98,7 +98,7 @@ pub mod tests {
let msg = AddNotificationRequest { let msg = AddNotificationRequest {
to: NAME2.into(), to: NAME2.into(),
heading: "Test notification".into(), heading: "Test notification".into(),
message: "Testeing notifications with a dummy message".into(), message: "Testing notifications with a dummy message".into(),
}; };
let send_notification_resp = test::call_service( let send_notification_resp = test::call_service(

View File

@@ -68,7 +68,7 @@ pub async fn get_notification(
id: Identity, id: Identity,
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
let receiver = id.identity().unwrap(); let receiver = id.identity().unwrap();
// TODO handle error where payload.to doesnt exist // TODO handle error where payload.to doesn't exist
let notifications = data.db.get_all_unread_notifications(&receiver).await?; let notifications = data.db.get_all_unread_notifications(&receiver).await?;
let notifications = NotificationResp::from_notifications(notifications); let notifications = NotificationResp::from_notifications(notifications);

View File

@@ -38,7 +38,7 @@ pub async fn mark_read(
id: Identity, id: Identity,
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
let receiver = id.identity().unwrap(); let receiver = id.identity().unwrap();
// TODO handle error where payload.to doesnt exist // TODO handle error where payload.to doesn't exist
// TODO get payload from path /api/v1/notifications/{id}/read" // TODO get payload from path /api/v1/notifications/{id}/read"
data.db data.db

View File

@@ -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(),

View File

@@ -16,6 +16,7 @@
*/ */
//! PoW Verification module //! PoW Verification module
use actix_web::HttpRequest;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use libmcaptcha::pow::Work; use libmcaptcha::pow::Work;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -31,18 +32,60 @@ 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
/// if verification is successful /// if verification is successful
#[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(
payload: web::Json<Work>, req: HttpRequest,
payload: web::Json<ApiWork>,
data: AppData, data: AppData,
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
#[cfg(not(test))]
let ip = req.connection_info().peer_addr().unwrap().to_string();
// From actix-web docs:
// Will only return None when called in unit tests unless TestRequest::peer_addr is used.
//
// ref: https://docs.rs/actix-web/latest/actix_web/struct.HttpRequest.html#method.peer_addr
#[cfg(test)]
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()).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))
} }
@@ -70,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";
@@ -118,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,
@@ -135,7 +259,7 @@ pub mod tests {
// .await; // .await;
// assert_eq!(pow_config_resp.status(), StatusCode::OK); // assert_eq!(pow_config_resp.status(), StatusCode::OK);
// I'm not checking for errors because changing work.result triggered // I'm not checking for errors because changing work.result triggered
// InssuficientDifficulty, which is possible becuase libmcaptcha calculates // InssuficientDifficulty, which is possible because libmcaptcha calculates
// difficulty with the submitted result. Besides, this endpoint is merely // difficulty with the submitted result. Besides, this endpoint is merely
// propagating errors from libmcaptcha and libmcaptcha has tests covering the // propagating errors from libmcaptcha and libmcaptcha has tests covering the
// pow aspects ¯\_(ツ)_/¯ // pow aspects ¯\_(ツ)_/¯

View File

@@ -47,7 +47,7 @@ impl From<VerifyCaptchaResultPayload> for VerifyCaptchaResult {
// API keys are mcaptcha actor names // API keys are mcaptcha actor names
/// route hander that validates a PoW solution token /// route handler that validates a PoW solution token
#[my_codegen::post(path = "V1_API_ROUTES.pow.validate_captcha_token()")] #[my_codegen::post(path = "V1_API_ROUTES.pow.validate_captcha_token()")]
pub async fn validate_captcha_token( pub async fn validate_captcha_token(
payload: web::Json<VerifyCaptchaResultPayload>, payload: web::Json<VerifyCaptchaResultPayload>,

View File

@@ -69,7 +69,7 @@ macro_rules! enum_system_wrapper {
/// Represents mCaptcha cache and master system. /// Represents mCaptcha cache and master system.
/// When Redis is configured, [SystemGroup::Redis] is used and /// When Redis is configured, [SystemGroup::Redis] is used and
/// in its absense, [SystemGroup::Embedded] is used /// in its absence, [SystemGroup::Embedded] is used
pub enum SystemGroup { pub enum SystemGroup {
Embedded(System<HashCache, EmbeddedMaster>), Embedded(System<HashCache, EmbeddedMaster>),
Redis(System<RedisCache, RedisMaster>), Redis(System<RedisCache, RedisMaster>),
@@ -83,7 +83,16 @@ 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]
enum_system_wrapper!(verify_pow, Work, CaptchaResult<String>); pub async fn verify_pow(
&self,
msg: Work,
ip: String,
) -> CaptchaResult<(String, u32)> {
match self {
Self::Embedded(val) => val.verify_pow(msg, ip).await,
Self::Redis(val) => val.verify_pow(msg, ip).await,
}
}
// utility function to validate verification tokens // utility function to validate verification tokens
enum_system_wrapper!( enum_system_wrapper!(
@@ -111,7 +120,18 @@ impl SystemGroup {
.build() .build()
.unwrap(); .unwrap();
SystemBuilder::default().pow(pow).cache(c).master(m).build() let runners = if let Some(runners) = s.captcha.runners {
runners
} else {
num_cpus::get_physical()
};
SystemBuilder::default()
.pow(pow)
.cache(c)
.master(m)
.runners(runners)
.queue_length(s.captcha.queue_length)
.build()
} }
// read settings, if Redis is configured then produce a Redis mCaptcha cache // read settings, if Redis is configured then produce a Redis mCaptcha cache
@@ -187,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 {

View File

@@ -111,7 +111,7 @@ mod tests {
use super::*; use super::*;
use crate::tests::*; use crate::tests::*;
const DURATION: u64 = 5; const DURATION: u64 = 25;
#[actix_rt::test] #[actix_rt::test]
async fn demo_account_works_pg() { async fn demo_account_works_pg() {
@@ -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(),
}; };

View File

@@ -71,7 +71,7 @@ const PAGE: &str = "Notifications";
)] )]
pub async fn notifications(data: AppData, id: Identity) -> PageResult<impl Responder> { pub async fn notifications(data: AppData, id: Identity) -> PageResult<impl Responder> {
let receiver = id.identity().unwrap(); let receiver = id.identity().unwrap();
// TODO handle error where payload.to doesnt exist // TODO handle error where payload.to doesn't exist
// let mut notifications = runner::get_notification(&data, &receiver).await?; // let mut notifications = runner::get_notification(&data, &receiver).await?;
let mut notifications = data.db.get_all_unread_notifications(&receiver).await?; let mut notifications = data.db.get_all_unread_notifications(&receiver).await?;

View File

@@ -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();

View File

@@ -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()

View File

@@ -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;
@@ -37,6 +37,8 @@ pub struct Server {
pub struct Captcha { pub struct Captcha {
pub salt: String, pub salt: String,
pub gc: u64, pub gc: u64,
pub runners: Option<usize>,
pub queue_length: usize,
pub enable_stats: bool, pub enable_stats: bool,
pub default_difficulty_strategy: DefaultDifficultyStrategy, pub default_difficulty_strategy: DefaultDifficultyStrategy,
} }
@@ -78,7 +80,7 @@ impl Server {
//impl DatabaseBuilder { //impl DatabaseBuilder {
// #[cfg(not(tarpaulin_include))] // #[cfg(not(tarpaulin_include))]
// fn extract_database_url(url: &Url) -> Self { // fn extract_database_url(url: &Url) -> Self {
// debug!("Databse name: {}", url.path()); // debug!("Database name: {}", url.path());
// let mut path = url.path().split('/'); // let mut path = url.path().split('/');
// path.next(); // path.next();
// let name = path.next().expect("no database name").to_string(); // let name = path.next().expect("no database name").to_string();
@@ -150,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") {
@@ -178,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);
} }
} }
@@ -213,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"),
urlencoding::encode(
s.get::<String>("database.password") s.get::<String>("database.password")
.expect("Couldn't access 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")
@@ -223,7 +238,7 @@ fn set_database_url(s: &mut Config) {
.expect("Couldn't access database name") .expect("Couldn't access database name")
), ),
) )
.expect("Couldn't set databse url"); .expect("Couldn't set database url");
} }
//#[cfg(test)] //#[cfg(test)]

View File

@@ -49,10 +49,11 @@ pub mod pg {
pub async fn get_data() -> ArcData { pub async fn get_data() -> ArcData {
let url = env::var("POSTGRES_DATABASE_URL").unwrap(); let url = env::var("POSTGRES_DATABASE_URL").unwrap();
let mut settings = get_settings(); let mut settings = get_settings();
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 {
@@ -67,10 +68,11 @@ pub mod maria {
pub async fn get_data() -> ArcData { pub async fn get_data() -> ArcData {
let url = env::var("MARIA_DATABASE_URL").unwrap(); let url = env::var("MARIA_DATABASE_URL").unwrap();
let mut settings = get_settings(); let mut settings = get_settings();
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 {
@@ -116,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) => {
@@ -260,5 +262,6 @@ pub fn get_level_data() -> CreateCaptcha {
levels, levels,
duration: 30, duration: 30,
description: "dummy".into(), description: "dummy".into(),
publish_benchmarks: false,
} }
} }

View File

@@ -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/>.
*/ */
* { * {

View File

@@ -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"); .>

View 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;
}

View File

@@ -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>

View File

@@ -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)}`);

View File

@@ -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>

View File

@@ -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;

View File

@@ -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

View File

@@ -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>

View File

@@ -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)}`);

View File

@@ -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"

View File

@@ -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"); .>

View File

@@ -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"
@@ -34,10 +35,12 @@
</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"
target="_blank"
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>"> href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
Privacy Privacy
</a> </a>
<a class="widget__mcaptcha-info-link" <a class="widget__mcaptcha-info-link"
target="_blank"
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>"> href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
Terms Terms
</a> </a>

View File

@@ -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

View File

@@ -1,27 +1,21 @@
/* /*
* 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 {
max-width: 300px; align-items: center;
max-height: 74px;
display: flex;
box-sizing: border-box; box-sizing: border-box;
display: flex;
height: 100%;
} }
.widget__noscript-container { .widget__noscript-container {
@@ -49,13 +43,10 @@
} }
.widget__verification-container { .widget__verification-container {
display: none;
/*display: flex; */
align-items: center; align-items: center;
display: none;
line-height: 30px; line-height: 30px;
flex: 2; font-size: 1rem;
margin: auto;
font-size: 0.99rem;
} }
.widget__verification-checkbox { .widget__verification-checkbox {
@@ -95,15 +86,10 @@
} }
.widget__mcaptcha-details { .widget__mcaptcha-details {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; margin-left: auto;
} margin-right: 10px;
.widget__mcaptcha-logo-container {
flex: 2;
padding-top: 3px;
} }
.widget__mcaptcha-brand-name { .widget__mcaptcha-brand-name {

View File

@@ -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

View File

@@ -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);

View File

@@ -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 = {

View File

@@ -533,6 +533,46 @@
"@types/yargs" "^16.0.0" "@types/yargs" "^16.0.0"
chalk "^4.0.0" chalk "^4.0.0"
"@jridgewell/gen-mapping@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@mcaptcha/core-glue@^0.1.0-alpha-3": "@mcaptcha/core-glue@^0.1.0-alpha-3":
version "0.1.0-alpha-3" version "0.1.0-alpha-3"
resolved "https://registry.yarnpkg.com/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-3.tgz#16c11cb3751b6421999353dc10c032afd08ffa8b" resolved "https://registry.yarnpkg.com/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-3.tgz#16c11cb3751b6421999353dc10c032afd08ffa8b"
@@ -1079,9 +1119,9 @@ acorn@^7.1.1:
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0: acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0:
version "8.5.0" version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
agent-base@6: agent-base@6:
version "6.0.2" version "6.0.2"
@@ -3830,9 +3870,9 @@ nanocolors@^0.1.12:
integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ== integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
nanoid@^3.1.28: nanoid@^3.1.28:
version "3.1.29" version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.29.tgz#214fb2d7a33e1a5bef4757b779dfaeb6a4e5aeb4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg== integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
natural-compare@^1.4.0: natural-compare@^1.4.0:
version "1.4.0" version "1.4.0"
@@ -4809,9 +4849,9 @@ source-map-js@^0.6.2:
integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
source-map-support@^0.5.6, source-map-support@~0.5.20: source-map-support@^0.5.6, source-map-support@~0.5.20:
version "0.5.20" version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies: dependencies:
buffer-from "^1.0.0" buffer-from "^1.0.0"
source-map "^0.6.0" source-map "^0.6.0"
@@ -4826,7 +4866,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3, source-map@~0.7.2: source-map@^0.7.3:
version "0.7.3" version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
@@ -5017,12 +5057,13 @@ terser-webpack-plugin@^5.1.3:
terser "^5.7.2" terser "^5.7.2"
terser@^5.7.2: terser@^5.7.2:
version "5.9.0" version "5.14.2"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies: dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0" commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.20" source-map-support "~0.5.20"
test-exclude@^6.0.0: test-exclude@^6.0.0: