mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2026-02-11 10:05:41 +00:00
Compare commits
49 Commits
update-dep
...
fix-151
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1fe45d409 | ||
|
|
59e339f287 | ||
|
|
ddcde9cf18 | ||
|
|
65c92ee96e | ||
|
|
40766ff44f | ||
|
|
ddc3008009 | ||
|
|
cba056aba6 | ||
|
|
16c975d2ec | ||
|
|
f67fdf917e | ||
|
|
e1746223c8 | ||
|
|
ae08c09702 | ||
|
|
1c9e242d7e | ||
|
|
3cb0ca38ec | ||
|
|
3cd38511fa | ||
|
|
d765bd7491 | ||
|
|
8e33e75659 | ||
|
|
c00857dd28 | ||
|
|
9cf0eb596a | ||
|
|
d010a1cbd4 | ||
|
|
453be36201 | ||
|
|
d4967626ee | ||
|
|
2ee0a0ae5f | ||
|
|
5722a5327c | ||
|
|
239e0bfd47 | ||
|
|
790fd8f393 | ||
|
|
c70a30e640 | ||
|
|
3b8051159d | ||
|
|
91c235b3f4 | ||
|
|
9bcf6af3ab | ||
|
|
e0d6188853 | ||
|
|
1b2096d955 | ||
|
|
13c3066b86 | ||
|
|
da934f5ba7 | ||
|
|
26ad05d284 | ||
|
|
b6326603d1 | ||
|
|
8bed3cb352 | ||
|
|
8e03290fda | ||
|
|
321fd2e89b | ||
|
|
36600e2f13 | ||
|
|
606d22cc9d | ||
|
|
4426057fbc | ||
|
|
1f23999c10 | ||
|
|
0a3d93453e | ||
|
|
939fb5f8b9 | ||
|
|
3a787a6592 | ||
|
|
9dfb0713ad | ||
|
|
ad4582cc16 | ||
|
|
77e4a9c473 | ||
|
|
b6497882d7 |
43
.env.docker-compose
Normal file
43
.env.docker-compose
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
MCAPTCHA_debug=false
|
||||||
|
MCAPTCHA_commercial=false
|
||||||
|
MCAPTCHA_source_code=https://github.com/mCaptcha/mCaptcha
|
||||||
|
MCAPTCHA_allow_registration=false
|
||||||
|
MCAPTCHA_allow_demo=false
|
||||||
|
|
||||||
|
# database
|
||||||
|
DATABASE_URL=postgres://postgres:password@mcaptcha_postgres:5432/postgres
|
||||||
|
MCAPTCHA_database_POOL=4
|
||||||
|
|
||||||
|
# redis
|
||||||
|
MCAPTCHA_redis_URL=redis://mcaptcha_redis
|
||||||
|
MCAPTCHA_redis_POOL=4
|
||||||
|
|
||||||
|
# server
|
||||||
|
PORT=7000
|
||||||
|
MCAPTCHA_server_DOMAIN=localhost
|
||||||
|
MCAPTCHA__server_COOKIE_SECRET=pleasereplacethiswithrandomstring # PLEASE SET RANDOM STRING. MIN LENGTH=32
|
||||||
|
MCAPTCHA__server_IP= 0.0.0.0
|
||||||
|
|
||||||
|
|
||||||
|
# captcha
|
||||||
|
MCAPTCHA_captcha_SALT=pleasereplacethiswithrandomstring # PLEASE SET RANDOM STRING. MIN LENGTH=32
|
||||||
|
MCAPTCHA_captcha_GC=30
|
||||||
|
MCAPTCHA_captcha_RUNNERS=4
|
||||||
|
MCAPTCHA_captcha_QUEUE_LENGTH=2000
|
||||||
|
MCAPTCHA_captcha_ENABLE_STATS=true
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty=50000 # almost instant solution
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty=3000000 # greater than 3.5s
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty=5000000 # roughly 1.5s
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration=30 # cooldown period in seconds
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time=1 # almost instant solution
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time=3
|
||||||
|
MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time=5
|
||||||
|
|
||||||
|
|
||||||
|
# SMTP
|
||||||
|
#MCAPTCHA_smtp_FROM=
|
||||||
|
#MCAPTCHA_smtp_REPLY=
|
||||||
|
#MCAPTCHA_smtp_URL=
|
||||||
|
#MCAPTCHA_smtp_USERNAME=
|
||||||
|
#MCAPTCHA_smtp_PASSWORD=
|
||||||
|
#MCAPTCHA_smtp_PORT=
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export POSTGRES_DATABASE_URL="postgres://postgres:password@localhost:5432/postgres"
|
export POSTGRES_DATABASE_URL="postgres://postgres:password@localhost:5432/postgres"
|
||||||
export MARIA_DATABASE_URL="mysql://maria:password@localhost:3306/maria"
|
export MARIA_DATABASE_URL="mysql://root:password@localhost:3306/maria"
|
||||||
|
|||||||
12
.github/workflows/linux.yml
vendored
12
.github/workflows/linux.yml
vendored
@@ -129,12 +129,12 @@ 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
|
- name: publish bins
|
||||||
# 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: ./scripts/publish.sh publish master latest $DUMBSERVE_PASSWORD
|
run: ./scripts/publish.sh publish master latest $DUMBSERVE_PASSWORD
|
||||||
# env:
|
env:
|
||||||
# DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
|
DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
|
||||||
# GPG_PASSWORD: ${{ secrets.GPG_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')
|
||||||
|
|||||||
125
.github/workflows/release.yml
vendored
Normal file
125
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
name: Publish release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
type: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
name: x86_64-unknown-linux-gnu
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
mcaptcha-redis:
|
||||||
|
image: mcaptcha/cache
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
mcaptcha-smtp:
|
||||||
|
image: maildev/maildev
|
||||||
|
env:
|
||||||
|
MAILDEV_WEB_PORT: "1080"
|
||||||
|
MAILDEV_INCOMING_USER: "admin"
|
||||||
|
MAILDEV_INCOMING_PASS: "password"
|
||||||
|
ports:
|
||||||
|
- 1080:1080
|
||||||
|
- 10025:1025
|
||||||
|
|
||||||
|
maria:
|
||||||
|
image: mariadb:10
|
||||||
|
env:
|
||||||
|
MARIADB_USER: "maria"
|
||||||
|
MARIADB_PASSWORD: "password"
|
||||||
|
MARIADB_ROOT_PASSWORD: "password"
|
||||||
|
MARIADB_DATABASE: "maria"
|
||||||
|
options: >-
|
||||||
|
--health-cmd="mysqladmin ping"
|
||||||
|
--health-interval=10s
|
||||||
|
--health-timeout=5s
|
||||||
|
--health-retries=10
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: configure GPG key
|
||||||
|
run: echo -n "$RELEASE_BOT_GPG_SIGNING_KEY" | gpg --batch --import --pinentry-mode loopback
|
||||||
|
env:
|
||||||
|
RELEASE_BOT_GPG_SIGNING_KEY: ${{ secrets.RELEASE_BOT_GPG_SIGNING_KEY }}
|
||||||
|
|
||||||
|
- name: Set release version
|
||||||
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: load env
|
||||||
|
run: |
|
||||||
|
source .env_sample \
|
||||||
|
&& echo "POSTGRES_DATABASE_URL=$POSTGRES_DATABASE_URL" >> $GITHUB_ENV \
|
||||||
|
&& echo "MARIA_DATABASE_URL=$MARIA_DATABASE_URL" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "20.0.0"
|
||||||
|
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: install nightwatch dep
|
||||||
|
run: sudo apt-get install xvfb
|
||||||
|
|
||||||
|
- name: Run migrations
|
||||||
|
run: make migrate
|
||||||
|
env:
|
||||||
|
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||||
|
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: make
|
||||||
|
env:
|
||||||
|
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||||
|
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||||
|
|
||||||
|
- name: lint frontend
|
||||||
|
run: yarn lint
|
||||||
|
|
||||||
|
- name: run tests
|
||||||
|
run: make test
|
||||||
|
env:
|
||||||
|
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||||
|
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||||
|
|
||||||
|
- name: run integration tests
|
||||||
|
run: make test.integration
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: mcaptcha
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: build docker images
|
||||||
|
run: docker build -t mcaptcha/mcaptcha:${RELEASE_VERSION} .
|
||||||
|
|
||||||
|
- name: publish docker images
|
||||||
|
run: docker push mcaptcha/mcaptcha:${RELEASE_VERSION}
|
||||||
|
|
||||||
|
- name: publish bins
|
||||||
|
run: ./scripts/publish.sh publish $RELEASE_VERSION latest $DUMBSERVE_PASSWORD
|
||||||
|
env:
|
||||||
|
DUMBSERVE_PASSWORD: ${{ secrets.DUMBSERVE_PASSWORD }}
|
||||||
|
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
|
||||||
64
.github/workflows/tagged-release.yml
vendored
64
.github/workflows/tagged-release.yml
vendored
@@ -1,32 +1,32 @@
|
|||||||
name: Create binary for release
|
#name: Create binary for release
|
||||||
|
#
|
||||||
# Only on tags that start with a "v"
|
## Only on tags that start with a "v"
|
||||||
on:
|
#on:
|
||||||
push:
|
# push:
|
||||||
tags:
|
# tags:
|
||||||
- "v*"
|
# - "v*"
|
||||||
|
#
|
||||||
jobs:
|
#jobs:
|
||||||
build:
|
# build:
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
strategy:
|
# strategy:
|
||||||
fail-fast: false
|
# fail-fast: false
|
||||||
matrix:
|
# matrix:
|
||||||
include:
|
# include:
|
||||||
- target: x86_64-pc-windows-gnu
|
# - target: x86_64-pc-windows-gnu
|
||||||
archive: zip
|
# archive: zip
|
||||||
- target: x86_64-unknown-linux-musl
|
# - target: x86_64-unknown-linux-musl
|
||||||
archive: tar.gz tar.xz
|
# archive: tar.gz tar.xz
|
||||||
- target: x86_64-apple-darwin
|
# - target: x86_64-apple-darwin
|
||||||
archive: zip
|
# archive: zip
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout
|
# - name: Checkout
|
||||||
uses: actions/checkout@v3
|
# uses: actions/checkout@v3
|
||||||
|
#
|
||||||
- name: Compile and release
|
# - name: Compile and release
|
||||||
uses: rust-build/rust-build.action@v1.3.2
|
# uses: rust-build/rust-build.action@v1.3.2
|
||||||
env:
|
# env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
# with:
|
||||||
RUSTTARGET: ${{ matrix.target }}
|
# RUSTTARGET: ${{ matrix.target }}
|
||||||
ARCHIVE_TYPES: ${{ matrix.archive }}
|
# ARCHIVE_TYPES: ${{ matrix.archive }}
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ RUN cargo build --release
|
|||||||
|
|
||||||
FROM debian:bookworm as mCaptcha
|
FROM debian:bookworm as mCaptcha
|
||||||
LABEL org.opencontainers.image.source https://github.com/mCaptcha/mCaptcha
|
LABEL org.opencontainers.image.source https://github.com/mCaptcha/mCaptcha
|
||||||
|
RUN set -ex; \
|
||||||
|
apt-get update; \
|
||||||
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
|
apt-get install -y --no-install-recommends curl
|
||||||
RUN useradd -ms /bin/bash -u 1001 mcaptcha
|
RUN useradd -ms /bin/bash -u 1001 mcaptcha
|
||||||
WORKDIR /home/mcaptcha
|
WORKDIR /home/mcaptcha
|
||||||
COPY --from=rust /src/target/release/mcaptcha /usr/local/bin/
|
COPY --from=rust /src/target/release/mcaptcha /usr/local/bin/
|
||||||
|
|||||||
@@ -34,8 +34,11 @@ enable_stats = true
|
|||||||
|
|
||||||
[captcha.default_difficulty_strategy]
|
[captcha.default_difficulty_strategy]
|
||||||
avg_traffic_difficulty = 50000 # almost instant solution
|
avg_traffic_difficulty = 50000 # almost instant solution
|
||||||
|
#avg_traffic_time = 1 # almost instant solution
|
||||||
peak_sustainable_traffic_difficulty = 3000000 # roughly 1.5s
|
peak_sustainable_traffic_difficulty = 3000000 # roughly 1.5s
|
||||||
|
#peak_sustainable_traffic_time = 3
|
||||||
broke_my_site_traffic_difficulty = 5000000 # greater than 3.5s
|
broke_my_site_traffic_difficulty = 5000000 # greater than 3.5s
|
||||||
|
#broke_my_site_traffic_time = 5
|
||||||
duration = 30 # cooldown period in seconds
|
duration = 30 # cooldown period in seconds
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
|
|||||||
@@ -202,6 +202,13 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
|||||||
captcha_key: &str,
|
captcha_key: &str,
|
||||||
) -> DBResult<TrafficPattern>;
|
) -> DBResult<TrafficPattern>;
|
||||||
|
|
||||||
|
/// Get all easy captcha configurations on instance
|
||||||
|
async fn get_all_easy_captchas(
|
||||||
|
&self,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<EasyCaptcha>>;
|
||||||
|
|
||||||
/// Delete traffic configuration
|
/// Delete traffic configuration
|
||||||
async fn delete_traffic_pattern(
|
async fn delete_traffic_pattern(
|
||||||
&self,
|
&self,
|
||||||
@@ -292,6 +299,32 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
|||||||
|
|
||||||
/// Get all psuedo IDs
|
/// Get all psuedo IDs
|
||||||
async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult<Vec<String>>;
|
async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult<Vec<String>>;
|
||||||
|
|
||||||
|
/// Track maximum nonce received against captcha levels
|
||||||
|
async fn update_max_nonce_for_level(
|
||||||
|
&self,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
latest_nonce: u32,
|
||||||
|
) -> DBResult<()>;
|
||||||
|
|
||||||
|
/// Get maximum nonce tracked so far for captcha levels
|
||||||
|
async fn get_max_nonce_for_level(
|
||||||
|
&self,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
) -> DBResult<u32>;
|
||||||
|
|
||||||
|
/// Get number of analytics entries that are under a certain duration
|
||||||
|
async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize>;
|
||||||
|
|
||||||
|
/// Get the entry at a location in the list of analytics entires under a certain time limit
|
||||||
|
/// and sorted in ascending order
|
||||||
|
async fn stats_get_entry_at_location_for_time_limit_asc(
|
||||||
|
&self,
|
||||||
|
duration: u32,
|
||||||
|
location: u32,
|
||||||
|
) -> DBResult<Option<usize>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||||
@@ -357,6 +390,19 @@ pub struct AddNotification<'a> {
|
|||||||
pub message: &'a str,
|
pub message: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||||
|
/// Represents Easy captcha configuration
|
||||||
|
pub struct EasyCaptcha {
|
||||||
|
/// traffic pattern of easy captcha
|
||||||
|
pub traffic_pattern: TrafficPattern,
|
||||||
|
/// captcha key/sitekey
|
||||||
|
pub key: String,
|
||||||
|
/// captcha description
|
||||||
|
pub description: String,
|
||||||
|
/// Owner of the captcha configuration
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||||
/// User's traffic pattern; used in generating a captcha configuration
|
/// User's traffic pattern; used in generating a captcha configuration
|
||||||
pub struct TrafficPattern {
|
pub struct TrafficPattern {
|
||||||
|
|||||||
@@ -7,6 +7,29 @@
|
|||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// easy traffic pattern
|
||||||
|
pub const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern {
|
||||||
|
avg_traffic: 500,
|
||||||
|
peak_sustainable_traffic: 5_000,
|
||||||
|
broke_my_site_traffic: Some(10_000),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// levels for complex captcha config
|
||||||
|
pub const LEVELS: [Level; 3] = [
|
||||||
|
Level {
|
||||||
|
difficulty_factor: 1,
|
||||||
|
visitor_threshold: 1,
|
||||||
|
},
|
||||||
|
Level {
|
||||||
|
difficulty_factor: 2,
|
||||||
|
visitor_threshold: 2,
|
||||||
|
},
|
||||||
|
Level {
|
||||||
|
difficulty_factor: 3,
|
||||||
|
visitor_threshold: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
/// test all database functions
|
/// test all database functions
|
||||||
pub async fn database_works<'a, T: MCDatabase>(
|
pub async fn database_works<'a, T: MCDatabase>(
|
||||||
db: &T,
|
db: &T,
|
||||||
@@ -200,6 +223,11 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||||||
tp
|
tp
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// get all traffic patterns
|
||||||
|
let patterns = db.get_all_easy_captchas(10, 0).await.unwrap();
|
||||||
|
assert_eq!(patterns.get(0).as_ref().unwrap().key, c.key);
|
||||||
|
assert_eq!(&patterns.get(0).unwrap().traffic_pattern, tp);
|
||||||
|
|
||||||
// delete traffic pattern
|
// delete traffic pattern
|
||||||
db.delete_traffic_pattern(p.username, c.key).await.unwrap();
|
db.delete_traffic_pattern(p.username, c.key).await.unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
@@ -250,7 +278,6 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||||||
db.record_confirm(c.key).await.unwrap();
|
db.record_confirm(c.key).await.unwrap();
|
||||||
|
|
||||||
// analytics start
|
// analytics start
|
||||||
|
|
||||||
db.analytics_create_psuedo_id_if_not_exists(c.key)
|
db.analytics_create_psuedo_id_if_not_exists(c.key)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -282,11 +309,31 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let analytics = CreatePerformanceAnalytics {
|
let analytics = CreatePerformanceAnalytics {
|
||||||
time: 0,
|
time: 1,
|
||||||
difficulty_factor: 0,
|
difficulty_factor: 1,
|
||||||
worker_type: "wasm".into(),
|
worker_type: "wasm".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.stats_get_num_logs_under_time(analytics.time)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
db.analysis_save(c.key, &analytics).await.unwrap();
|
db.analysis_save(c.key, &analytics).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.stats_get_num_logs_under_time(analytics.time)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.stats_get_num_logs_under_time(analytics.time - 1)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
0
|
||||||
|
);
|
||||||
let limit = 50;
|
let limit = 50;
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let a = db.analytics_fetch(c.key, limit, offset).await.unwrap();
|
let a = db.analytics_fetch(c.key, limit, offset).await.unwrap();
|
||||||
@@ -305,11 +352,82 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(db.analytics_fetch(c.key, 1000, 0).await.unwrap().len(), 0);
|
assert_eq!(db.analytics_fetch(c.key, 1000, 0).await.unwrap().len(), 0);
|
||||||
assert!(!db.analytics_captcha_is_published(c.key).await.unwrap());
|
assert!(!db.analytics_captcha_is_published(c.key).await.unwrap());
|
||||||
|
|
||||||
|
let rest_analytics = [
|
||||||
|
CreatePerformanceAnalytics {
|
||||||
|
time: 2,
|
||||||
|
difficulty_factor: 2,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
},
|
||||||
|
CreatePerformanceAnalytics {
|
||||||
|
time: 3,
|
||||||
|
difficulty_factor: 3,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
},
|
||||||
|
CreatePerformanceAnalytics {
|
||||||
|
time: 4,
|
||||||
|
difficulty_factor: 4,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
},
|
||||||
|
CreatePerformanceAnalytics {
|
||||||
|
time: 5,
|
||||||
|
difficulty_factor: 5,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for a in rest_analytics.iter() {
|
||||||
|
db.analysis_save(c.key, &a).await.unwrap();
|
||||||
|
}
|
||||||
|
assert!(db
|
||||||
|
.stats_get_entry_at_location_for_time_limit_asc(1, 2)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
db.stats_get_entry_at_location_for_time_limit_asc(2, 1)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
Some(2)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.stats_get_entry_at_location_for_time_limit_asc(3, 2)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
Some(3)
|
||||||
|
);
|
||||||
|
|
||||||
db.analytics_delete_all_records_for_campaign(c.key)
|
db.analytics_delete_all_records_for_campaign(c.key)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// analytics end
|
// analytics end
|
||||||
|
|
||||||
|
// nonce tracking start
|
||||||
|
assert_eq!(
|
||||||
|
db.get_max_nonce_for_level(c.key, l[0].difficulty_factor)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
db.update_max_nonce_for_level(c.key, l[0].difficulty_factor, 1000)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.get_max_nonce_for_level(c.key, l[0].difficulty_factor)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
db.update_max_nonce_for_level(c.key, l[0].difficulty_factor, 10_000)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
db.get_max_nonce_for_level(c.key, l[0].difficulty_factor)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
10_000
|
||||||
|
);
|
||||||
|
// nonce tracking end
|
||||||
|
|
||||||
assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1);
|
assert_eq!(db.fetch_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)
|
||||||
|
|||||||
12
db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json
generated
Normal file
12
db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n AND\n visitor_threshold = ?\n ), ?);",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc"
|
||||||
|
}
|
||||||
12
db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json
generated
Normal file
12
db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "UPDATE mcaptcha_track_nonce SET nonce = ?\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n )\n AND nonce <= ?;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea"
|
||||||
|
}
|
||||||
25
db/db-sqlx-maria/.sqlx/query-9bae79667a8cc631541879321e72a40f20cf812584aaf44418089bc7a51e07c4.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-9bae79667a8cc631541879321e72a40f20cf812584aaf44418089bc7a51e07c4.json
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "SELECT\n COUNT(difficulty_factor) AS count\n FROM\n mcaptcha_pow_analytics\n WHERE time <= ?;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "count",
|
||||||
|
"type_info": {
|
||||||
|
"type": "LongLong",
|
||||||
|
"flags": "NOT_NULL | BINARY",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 21
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "9bae79667a8cc631541879321e72a40f20cf812584aaf44418089bc7a51e07c4"
|
||||||
|
}
|
||||||
12
db/db-sqlx-maria/.sqlx/query-9def82dcec9c8d477824182bb2f71044cc264cf2073ab4f60a0000b435ed0f0b.json
generated
Normal file
12
db/db-sqlx-maria/.sqlx/query-9def82dcec9c8d477824182bb2f71044cc264cf2073ab4f60a0000b435ed0f0b.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key =?)\n AND\n difficulty_factor = ?\n ), ?);",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "9def82dcec9c8d477824182bb2f71044cc264cf2073ab4f60a0000b435ed0f0b"
|
||||||
|
}
|
||||||
25
db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "SELECT nonce FROM mcaptcha_track_nonce\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n );",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "nonce",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "NOT_NULL",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb"
|
||||||
|
}
|
||||||
25
db/db-sqlx-maria/.sqlx/query-c4d6ad934e38218931e74ae1c31c6712cbadb40f31bb12e160c9d333c7e3835c.json
generated
Normal file
25
db/db-sqlx-maria/.sqlx/query-c4d6ad934e38218931e74ae1c31c6712cbadb40f31bb12e160c9d333c7e3835c.json
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "SELECT\n difficulty_factor\n FROM\n mcaptcha_pow_analytics\n WHERE\n time <= ?\n ORDER BY difficulty_factor ASC LIMIT 1 OFFSET ?;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "difficulty_factor",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "c4d6ad934e38218931e74ae1c31c6712cbadb40f31bb12e160c9d333c7e3835c"
|
||||||
|
}
|
||||||
80
db/db-sqlx-maria/.sqlx/query-d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64.json
generated
Normal file
80
db/db-sqlx-maria/.sqlx/query-d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64.json
generated
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "SELECT \n mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,\n mcaptcha_config.name,\n mcaptcha_users.name as username,\n mcaptcha_config.captcha_key\n FROM \n mcaptcha_sitekey_user_provided_avg_traffic \n INNER JOIN\n mcaptcha_config\n ON\n mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id\n INNER JOIN\n mcaptcha_users\n ON\n mcaptcha_config.user_id = mcaptcha_users.ID\n ORDER BY mcaptcha_config.config_id\n LIMIT ? OFFSET ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "avg_traffic",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "peak_sustainable_traffic",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "broke_my_site_traffic",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "name",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 224,
|
||||||
|
"max_size": 400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "username",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 224,
|
||||||
|
"max_size": 400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "captcha_key",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 224,
|
||||||
|
"max_size": 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64"
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_track_nonce (
|
||||||
|
level_id INTEGER NOT NULL,
|
||||||
|
nonce INTEGER NOT NULL DEFAULT 0,
|
||||||
|
ID INT auto_increment,
|
||||||
|
PRIMARY KEY(ID),
|
||||||
|
CONSTRAINT `fk_mcaptcha_track_nonce_level_id`
|
||||||
|
FOREIGN KEY (level_id)
|
||||||
|
REFERENCES mcaptcha_levels (level_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
);
|
||||||
@@ -43,7 +43,6 @@ pub mod dev {
|
|||||||
pub use super::errors::*;
|
pub use super::errors::*;
|
||||||
pub use super::Database;
|
pub use super::Database;
|
||||||
pub use db_core::dev::*;
|
pub use db_core::dev::*;
|
||||||
pub use prelude::*;
|
|
||||||
pub use sqlx::Error;
|
pub use sqlx::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,6 +432,39 @@ impl MCDatabase for Database {
|
|||||||
futs.push(fut);
|
futs.push(fut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try_join_all(futs)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
let mut futs = Vec::with_capacity(levels.len());
|
||||||
|
|
||||||
|
for level in levels.iter() {
|
||||||
|
let difficulty_factor = level.difficulty_factor as i32;
|
||||||
|
let visitor_threshold = level.visitor_threshold as i32;
|
||||||
|
let fut = sqlx::query!(
|
||||||
|
"INSERT INTO
|
||||||
|
mcaptcha_track_nonce (level_id, nonce)
|
||||||
|
VALUES ((
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
|
||||||
|
AND
|
||||||
|
difficulty_factor = ?
|
||||||
|
AND
|
||||||
|
visitor_threshold = ?
|
||||||
|
), ?);",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor,
|
||||||
|
visitor_threshold,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.execute(&self.pool);
|
||||||
|
futs.push(fut);
|
||||||
|
}
|
||||||
|
|
||||||
try_join_all(futs)
|
try_join_all(futs)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
@@ -1087,6 +1119,220 @@ impl MCDatabase for Database {
|
|||||||
|
|
||||||
Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
|
Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Track maximum nonce received against captcha levels
|
||||||
|
async fn update_max_nonce_for_level(
|
||||||
|
&self,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
latest_nonce: u32,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let latest_nonce = latest_nonce as i64;
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE mcaptcha_track_nonce SET nonce = ?
|
||||||
|
WHERE level_id = (
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
|
||||||
|
AND
|
||||||
|
difficulty_factor = ?
|
||||||
|
)
|
||||||
|
AND nonce <= ?;",
|
||||||
|
latest_nonce,
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor as i64,
|
||||||
|
latest_nonce
|
||||||
|
)
|
||||||
|
.execute(&self.pool).await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get maximum nonce tracked so far for captcha levels
|
||||||
|
async fn get_max_nonce_for_level(
|
||||||
|
&self,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
) -> DBResult<u32> {
|
||||||
|
struct X {
|
||||||
|
nonce: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_get_max_nonce(
|
||||||
|
pool: &MySqlPool,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
) -> DBResult<X> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
X,
|
||||||
|
"SELECT nonce FROM mcaptcha_track_nonce
|
||||||
|
WHERE level_id = (
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)
|
||||||
|
AND
|
||||||
|
difficulty_factor = ?
|
||||||
|
);",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor as i32,
|
||||||
|
)
|
||||||
|
.fetch_one(pool).await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await;
|
||||||
|
if let Err(DBError::CaptchaNotFound) = res {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO
|
||||||
|
mcaptcha_track_nonce (level_id, nonce)
|
||||||
|
VALUES ((
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key =?)
|
||||||
|
AND
|
||||||
|
difficulty_factor = ?
|
||||||
|
), ?);",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor as i32,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
let res =
|
||||||
|
inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await?;
|
||||||
|
Ok(res.nonce as u32)
|
||||||
|
} else {
|
||||||
|
let res = res?;
|
||||||
|
Ok(res.nonce as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get number of analytics entries that are under a certain duration
|
||||||
|
async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize> {
|
||||||
|
struct Count {
|
||||||
|
count: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//"SELECT COUNT(*) FROM (SELECT difficulty_factor FROM mcaptcha_pow_analytics WHERE time <= ?) as count",
|
||||||
|
let count = sqlx::query_as!(
|
||||||
|
Count,
|
||||||
|
"SELECT
|
||||||
|
COUNT(difficulty_factor) AS count
|
||||||
|
FROM
|
||||||
|
mcaptcha_pow_analytics
|
||||||
|
WHERE time <= ?;",
|
||||||
|
duration as i32,
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(count.count.unwrap_or_else(|| 0) as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the entry at a location in the list of analytics entires under a certain time limited
|
||||||
|
/// and sorted in ascending order
|
||||||
|
async fn stats_get_entry_at_location_for_time_limit_asc(
|
||||||
|
&self,
|
||||||
|
duration: u32,
|
||||||
|
location: u32,
|
||||||
|
) -> DBResult<Option<usize>> {
|
||||||
|
struct Difficulty {
|
||||||
|
difficulty_factor: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
match sqlx::query_as!(
|
||||||
|
Difficulty,
|
||||||
|
"SELECT
|
||||||
|
difficulty_factor
|
||||||
|
FROM
|
||||||
|
mcaptcha_pow_analytics
|
||||||
|
WHERE
|
||||||
|
time <= ?
|
||||||
|
ORDER BY difficulty_factor ASC LIMIT 1 OFFSET ?;",
|
||||||
|
duration as i32,
|
||||||
|
location as i64 - 1,
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => Ok(Some(res.difficulty_factor.unwrap() as usize)),
|
||||||
|
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||||
|
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all easy captcha configurations on instance
|
||||||
|
async fn get_all_easy_captchas(
|
||||||
|
&self,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<EasyCaptcha>> {
|
||||||
|
struct InnerEasyCaptcha {
|
||||||
|
captcha_key: String,
|
||||||
|
name: String,
|
||||||
|
username: String,
|
||||||
|
peak_sustainable_traffic: i32,
|
||||||
|
avg_traffic: i32,
|
||||||
|
broke_my_site_traffic: Option<i32>,
|
||||||
|
}
|
||||||
|
let mut inner_res = sqlx::query_as!(
|
||||||
|
InnerEasyCaptcha,
|
||||||
|
"SELECT
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
|
||||||
|
mcaptcha_config.name,
|
||||||
|
mcaptcha_users.name as username,
|
||||||
|
mcaptcha_config.captcha_key
|
||||||
|
FROM
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_config
|
||||||
|
ON
|
||||||
|
mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_users
|
||||||
|
ON
|
||||||
|
mcaptcha_config.user_id = mcaptcha_users.ID
|
||||||
|
ORDER BY mcaptcha_config.config_id
|
||||||
|
LIMIT ? OFFSET ?",
|
||||||
|
limit as i64,
|
||||||
|
offset as i64
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||||
|
let mut res = Vec::with_capacity(inner_res.len());
|
||||||
|
inner_res.drain(0..).for_each(|v| {
|
||||||
|
res.push(EasyCaptcha {
|
||||||
|
key: v.captcha_key,
|
||||||
|
description: v.name,
|
||||||
|
username: v.username,
|
||||||
|
traffic_pattern: TrafficPattern {
|
||||||
|
broke_my_site_traffic: v
|
||||||
|
.broke_my_site_traffic
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| *v as u32),
|
||||||
|
avg_traffic: v.avg_traffic as u32,
|
||||||
|
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
|
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
use sqlx::mysql::MySqlPoolOptions;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use sqlx::{migrate::MigrateDatabase, mysql::MySqlPoolOptions};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use db_core::tests::*;
|
use db_core::tests::*;
|
||||||
@@ -26,28 +28,6 @@ async fn everyting_works() {
|
|||||||
const HEADING: &str = "testing notifications get db mariadb";
|
const HEADING: &str = "testing notifications get db mariadb";
|
||||||
const MESSAGE: &str = "testing notifications get message db mariadb";
|
const MESSAGE: &str = "testing notifications get message db mariadb";
|
||||||
|
|
||||||
// easy traffic pattern
|
|
||||||
const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern {
|
|
||||||
avg_traffic: 500,
|
|
||||||
peak_sustainable_traffic: 5_000,
|
|
||||||
broke_my_site_traffic: Some(10_000),
|
|
||||||
};
|
|
||||||
|
|
||||||
const LEVELS: [Level; 3] = [
|
|
||||||
Level {
|
|
||||||
difficulty_factor: 1,
|
|
||||||
visitor_threshold: 1,
|
|
||||||
},
|
|
||||||
Level {
|
|
||||||
difficulty_factor: 2,
|
|
||||||
visitor_threshold: 2,
|
|
||||||
},
|
|
||||||
Level {
|
|
||||||
difficulty_factor: 3,
|
|
||||||
visitor_threshold: 3,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const ADD_NOTIFICATION: AddNotification = AddNotification {
|
const ADD_NOTIFICATION: AddNotification = AddNotification {
|
||||||
from: NAME,
|
from: NAME,
|
||||||
to: NAME,
|
to: NAME,
|
||||||
@@ -56,10 +36,20 @@ async fn everyting_works() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let url = env::var("MARIA_DATABASE_URL").unwrap();
|
let url = env::var("MARIA_DATABASE_URL").unwrap();
|
||||||
|
|
||||||
|
let mut parsed = Url::parse(&url).unwrap();
|
||||||
|
parsed.set_path("db_maria_test");
|
||||||
|
let url = parsed.to_string();
|
||||||
|
|
||||||
|
if sqlx::MySql::database_exists(&url).await.unwrap() {
|
||||||
|
sqlx::MySql::drop_database(&url).await.unwrap();
|
||||||
|
}
|
||||||
|
sqlx::MySql::create_database(&url).await.unwrap();
|
||||||
|
|
||||||
let pool_options = MySqlPoolOptions::new().max_connections(2);
|
let pool_options = MySqlPoolOptions::new().max_connections(2);
|
||||||
let connection_options = ConnectionOptions::Fresh(Fresh {
|
let connection_options = ConnectionOptions::Fresh(Fresh {
|
||||||
pool_options,
|
pool_options,
|
||||||
url,
|
url: url.clone(),
|
||||||
disable_logging: false,
|
disable_logging: false,
|
||||||
});
|
});
|
||||||
let db = connection_options.connect().await.unwrap();
|
let db = connection_options.connect().await.unwrap();
|
||||||
@@ -78,4 +68,6 @@ async fn everyting_works() {
|
|||||||
description: CAPTCHA_DESCRIPTION,
|
description: CAPTCHA_DESCRIPTION,
|
||||||
};
|
};
|
||||||
database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await;
|
database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await;
|
||||||
|
drop(db);
|
||||||
|
sqlx::MySql::drop_database(&url).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n AND\n visitor_threshold = $3\n ), $4);",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int4",
|
||||||
|
"Int4",
|
||||||
|
"Int4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "133ee23ab5ac7c664a86b6edfaa8da79281b6d1f5ba33c642a6ea1b0682fe0b0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT nonce FROM mcaptcha_track_nonce\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n );",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "nonce",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "96f1f1e45144d5add6c4ba4cd2df8eda6043bc8cd6952787f92a687fef778a6e"
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT COUNT(difficulty_factor) FROM mcaptcha_pow_analytics WHERE time <= $1;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "count",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "c08c1dd4bfcb6cbd0359c79cc3be79526a012b006ce9deb80bceb4e1a04c835d"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT\n difficulty_factor\n FROM\n mcaptcha_pow_analytics\n WHERE\n time <= $1\n ORDER BY difficulty_factor ASC LIMIT 1 OFFSET $2;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "difficulty_factor",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int4",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "c67aec0c3d5786fb495b6ed60fa106437d8e5034d3a40bf8face2ca7c12f2694"
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n ), $3);",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int4",
|
||||||
|
"Int4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "e0088cf77c1c3a0184f35d1899a6168023fba021adf281cf1c8f9e8ccfe3a03e"
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE mcaptcha_track_nonce SET nonce = $3\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n )\n AND nonce <= $3;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int4",
|
||||||
|
"Int4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "e33ee14cf76cd09d9a157b8784a3fe25b89eaca105aa30e479d31b756cd5c88b"
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT \n mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,\n mcaptcha_config.name,\n mcaptcha_users.name as username,\n mcaptcha_config.key\n FROM \n mcaptcha_sitekey_user_provided_avg_traffic \n INNER JOIN\n mcaptcha_config\n ON\n mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id\n INNER JOIN\n mcaptcha_users\n ON\n mcaptcha_config.user_id = mcaptcha_users.ID\n ORDER BY mcaptcha_config.config_id\n OFFSET $1 LIMIT $2; ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "avg_traffic",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "peak_sustainable_traffic",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "broke_my_site_traffic",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "name",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "username",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "key",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "f01a9c09c8722bc195f477a8c3ce6466d415e7c74665fa882eff4a8566e70577"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_track_nonce (
|
||||||
|
nonce INTEGER NOT NULL DEFAULT 0,
|
||||||
|
level_id INTEGER references mcaptcha_levels(level_id) ON DELETE CASCADE,
|
||||||
|
ID SERIAL PRIMARY KEY NOT NULL
|
||||||
|
);
|
||||||
@@ -43,7 +43,6 @@ pub mod dev {
|
|||||||
pub use super::errors::*;
|
pub use super::errors::*;
|
||||||
pub use super::Database;
|
pub use super::Database;
|
||||||
pub use db_core::dev::*;
|
pub use db_core::dev::*;
|
||||||
pub use prelude::*;
|
|
||||||
pub use sqlx::Error;
|
pub use sqlx::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,6 +444,38 @@ impl MCDatabase for Database {
|
|||||||
futs.push(fut);
|
futs.push(fut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try_join_all(futs)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
let mut futs = Vec::with_capacity(levels.len());
|
||||||
|
for level in levels.iter() {
|
||||||
|
let difficulty_factor = level.difficulty_factor as i32;
|
||||||
|
let visitor_threshold = level.visitor_threshold as i32;
|
||||||
|
let fut = sqlx::query!(
|
||||||
|
"INSERT INTO
|
||||||
|
mcaptcha_track_nonce (level_id, nonce)
|
||||||
|
VALUES ((
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||||
|
AND
|
||||||
|
difficulty_factor = $2
|
||||||
|
AND
|
||||||
|
visitor_threshold = $3
|
||||||
|
), $4);",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor,
|
||||||
|
visitor_threshold,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.execute(&self.pool);
|
||||||
|
futs.push(fut);
|
||||||
|
}
|
||||||
|
|
||||||
try_join_all(futs)
|
try_join_all(futs)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
@@ -638,13 +669,8 @@ impl MCDatabase for Database {
|
|||||||
username: &str,
|
username: &str,
|
||||||
captcha_key: &str,
|
captcha_key: &str,
|
||||||
) -> DBResult<TrafficPattern> {
|
) -> DBResult<TrafficPattern> {
|
||||||
struct Traffic {
|
|
||||||
peak_sustainable_traffic: i32,
|
|
||||||
avg_traffic: i32,
|
|
||||||
broke_my_site_traffic: Option<i32>,
|
|
||||||
}
|
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
Traffic,
|
InnerTraffic,
|
||||||
"SELECT
|
"SELECT
|
||||||
avg_traffic,
|
avg_traffic,
|
||||||
peak_sustainable_traffic,
|
peak_sustainable_traffic,
|
||||||
@@ -675,11 +701,67 @@ impl MCDatabase for Database {
|
|||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||||
Ok(TrafficPattern {
|
Ok(res.into())
|
||||||
broke_my_site_traffic: res.broke_my_site_traffic.as_ref().map(|v| *v as u32),
|
}
|
||||||
avg_traffic: res.avg_traffic as u32,
|
|
||||||
peak_sustainable_traffic: res.peak_sustainable_traffic as u32,
|
/// Get all easy captcha configurations on instance
|
||||||
})
|
async fn get_all_easy_captchas(
|
||||||
|
&self,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<EasyCaptcha>> {
|
||||||
|
struct InnerEasyCaptcha {
|
||||||
|
key: String,
|
||||||
|
peak_sustainable_traffic: i32,
|
||||||
|
avg_traffic: i32,
|
||||||
|
broke_my_site_traffic: Option<i32>,
|
||||||
|
name: String,
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
let mut inner_res = sqlx::query_as!(
|
||||||
|
InnerEasyCaptcha,
|
||||||
|
"SELECT
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
|
||||||
|
mcaptcha_config.name,
|
||||||
|
mcaptcha_users.name as username,
|
||||||
|
mcaptcha_config.key
|
||||||
|
FROM
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_config
|
||||||
|
ON
|
||||||
|
mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_users
|
||||||
|
ON
|
||||||
|
mcaptcha_config.user_id = mcaptcha_users.ID
|
||||||
|
ORDER BY mcaptcha_config.config_id
|
||||||
|
OFFSET $1 LIMIT $2; ",
|
||||||
|
offset as i32,
|
||||||
|
limit as i32
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||||
|
let mut res = Vec::with_capacity(inner_res.len());
|
||||||
|
inner_res.drain(0..).for_each(|v| {
|
||||||
|
res.push(EasyCaptcha {
|
||||||
|
key: v.key,
|
||||||
|
description: v.name,
|
||||||
|
username: v.username,
|
||||||
|
traffic_pattern: TrafficPattern {
|
||||||
|
broke_my_site_traffic: v
|
||||||
|
.broke_my_site_traffic
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| *v as u32),
|
||||||
|
avg_traffic: v.avg_traffic as u32,
|
||||||
|
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete traffic configuration
|
/// Delete traffic configuration
|
||||||
@@ -1097,6 +1179,154 @@ impl MCDatabase for Database {
|
|||||||
|
|
||||||
Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
|
Ok(res.drain(0..).map(|r| r.psuedo_id).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Track maximum nonce received against captcha levels
|
||||||
|
async fn update_max_nonce_for_level(
|
||||||
|
&self,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
latest_nonce: u32,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE mcaptcha_track_nonce SET nonce = $3
|
||||||
|
WHERE level_id = (
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||||
|
AND
|
||||||
|
difficulty_factor = $2
|
||||||
|
)
|
||||||
|
AND nonce <= $3;",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor as i32,
|
||||||
|
latest_nonce as i32,
|
||||||
|
)
|
||||||
|
.execute(&self.pool).await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get maximum nonce tracked so far for captcha levels
|
||||||
|
async fn get_max_nonce_for_level(
|
||||||
|
&self,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
) -> DBResult<u32> {
|
||||||
|
struct X {
|
||||||
|
nonce: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner_get_max_nonce(
|
||||||
|
pool: &PgPool,
|
||||||
|
captcha_key: &str,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
) -> DBResult<X> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
X,
|
||||||
|
"SELECT nonce FROM mcaptcha_track_nonce
|
||||||
|
WHERE level_id = (
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||||
|
AND
|
||||||
|
difficulty_factor = $2
|
||||||
|
);",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor as i32,
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await;
|
||||||
|
if let Err(DBError::CaptchaNotFound) = res {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO
|
||||||
|
mcaptcha_track_nonce (level_id, nonce)
|
||||||
|
VALUES ((
|
||||||
|
SELECT
|
||||||
|
level_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_levels
|
||||||
|
WHERE
|
||||||
|
config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))
|
||||||
|
AND
|
||||||
|
difficulty_factor = $2
|
||||||
|
), $3);",
|
||||||
|
&captcha_key,
|
||||||
|
difficulty_factor as i32,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
let res =
|
||||||
|
inner_get_max_nonce(&self.pool, captcha_key, difficulty_factor).await?;
|
||||||
|
Ok(res.nonce as u32)
|
||||||
|
} else {
|
||||||
|
let res = res?;
|
||||||
|
Ok(res.nonce as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get number of analytics entries that are under a certain duration
|
||||||
|
async fn stats_get_num_logs_under_time(&self, duration: u32) -> DBResult<usize> {
|
||||||
|
struct Count {
|
||||||
|
count: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = sqlx::query_as!(
|
||||||
|
Count,
|
||||||
|
"SELECT COUNT(difficulty_factor) FROM mcaptcha_pow_analytics WHERE time <= $1;",
|
||||||
|
duration as i32,
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?;
|
||||||
|
|
||||||
|
Ok(count.count.unwrap_or_else(|| 0) as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the entry at a location in the list of analytics entires under a certain time limit
|
||||||
|
/// and sorted in ascending order
|
||||||
|
async fn stats_get_entry_at_location_for_time_limit_asc(
|
||||||
|
&self,
|
||||||
|
duration: u32,
|
||||||
|
location: u32,
|
||||||
|
) -> DBResult<Option<usize>> {
|
||||||
|
struct Difficulty {
|
||||||
|
difficulty_factor: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
match sqlx::query_as!(
|
||||||
|
Difficulty,
|
||||||
|
"SELECT
|
||||||
|
difficulty_factor
|
||||||
|
FROM
|
||||||
|
mcaptcha_pow_analytics
|
||||||
|
WHERE
|
||||||
|
time <= $1
|
||||||
|
ORDER BY difficulty_factor ASC LIMIT 1 OFFSET $2;",
|
||||||
|
duration as i32,
|
||||||
|
location as i64 - 1,
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => Ok(Some(res.difficulty_factor.unwrap() as usize)),
|
||||||
|
Err(sqlx::Error::RowNotFound) => Ok(None),
|
||||||
|
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -1166,3 +1396,19 @@ impl From<InternaleCaptchaConfig> for Captcha {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InnerTraffic {
|
||||||
|
peak_sustainable_traffic: i32,
|
||||||
|
avg_traffic: i32,
|
||||||
|
broke_my_site_traffic: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InnerTraffic> for TrafficPattern {
|
||||||
|
fn from(v: InnerTraffic) -> Self {
|
||||||
|
TrafficPattern {
|
||||||
|
broke_my_site_traffic: v.broke_my_site_traffic.as_ref().map(|v| *v as u32),
|
||||||
|
avg_traffic: v.avg_traffic as u32,
|
||||||
|
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@
|
|||||||
|
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
|
|
||||||
use sqlx::postgres::PgPoolOptions;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use sqlx::migrate::MigrateDatabase;
|
||||||
|
use sqlx::postgres::PgPoolOptions;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use db_core::tests::*;
|
use db_core::tests::*;
|
||||||
@@ -26,28 +29,6 @@ async fn everyting_works() {
|
|||||||
const HEADING: &str = "testing notifications get db postgres";
|
const HEADING: &str = "testing notifications get db postgres";
|
||||||
const MESSAGE: &str = "testing notifications get message db postgres";
|
const MESSAGE: &str = "testing notifications get message db postgres";
|
||||||
|
|
||||||
// easy traffic pattern
|
|
||||||
const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern {
|
|
||||||
avg_traffic: 500,
|
|
||||||
peak_sustainable_traffic: 5_000,
|
|
||||||
broke_my_site_traffic: Some(10_000),
|
|
||||||
};
|
|
||||||
|
|
||||||
const LEVELS: [Level; 3] = [
|
|
||||||
Level {
|
|
||||||
difficulty_factor: 1,
|
|
||||||
visitor_threshold: 1,
|
|
||||||
},
|
|
||||||
Level {
|
|
||||||
difficulty_factor: 2,
|
|
||||||
visitor_threshold: 2,
|
|
||||||
},
|
|
||||||
Level {
|
|
||||||
difficulty_factor: 3,
|
|
||||||
visitor_threshold: 3,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const ADD_NOTIFICATION: AddNotification = AddNotification {
|
const ADD_NOTIFICATION: AddNotification = AddNotification {
|
||||||
from: NAME,
|
from: NAME,
|
||||||
to: NAME,
|
to: NAME,
|
||||||
@@ -56,10 +37,20 @@ async fn everyting_works() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let url = env::var("POSTGRES_DATABASE_URL").unwrap();
|
let url = env::var("POSTGRES_DATABASE_URL").unwrap();
|
||||||
|
|
||||||
|
let mut parsed = Url::parse(&url).unwrap();
|
||||||
|
parsed.set_path("db_postgres_test");
|
||||||
|
let url = parsed.to_string();
|
||||||
|
|
||||||
|
if sqlx::Postgres::database_exists(&url).await.unwrap() {
|
||||||
|
sqlx::Postgres::drop_database(&url).await.unwrap();
|
||||||
|
}
|
||||||
|
sqlx::Postgres::create_database(&url).await.unwrap();
|
||||||
|
|
||||||
let pool_options = PgPoolOptions::new().max_connections(2);
|
let pool_options = PgPoolOptions::new().max_connections(2);
|
||||||
let connection_options = ConnectionOptions::Fresh(Fresh {
|
let connection_options = ConnectionOptions::Fresh(Fresh {
|
||||||
pool_options,
|
pool_options,
|
||||||
url,
|
url: url.clone(),
|
||||||
disable_logging: false,
|
disable_logging: false,
|
||||||
});
|
});
|
||||||
let db = connection_options.connect().await.unwrap();
|
let db = connection_options.connect().await.unwrap();
|
||||||
@@ -78,4 +69,6 @@ async fn everyting_works() {
|
|||||||
description: CAPTCHA_DESCRIPTION,
|
description: CAPTCHA_DESCRIPTION,
|
||||||
};
|
};
|
||||||
database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await;
|
database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await;
|
||||||
|
drop(db);
|
||||||
|
sqlx::Postgres::drop_database(&url).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ services:
|
|||||||
image: mcaptcha/mcaptcha:latest
|
image: mcaptcha/mcaptcha:latest
|
||||||
ports:
|
ports:
|
||||||
- 7000:7000
|
- 7000:7000
|
||||||
environment:
|
env_file:
|
||||||
DATABASE_URL: postgres://postgres:password@mcaptcha_postgres:5432/postgres # set password at placeholder
|
- .env.docker-compose
|
||||||
MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
|
|
||||||
RUST_LOG: "debug"
|
|
||||||
PORT: 7000
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- mcaptcha_postgres
|
- mcaptcha_postgres
|
||||||
- mcaptcha_redis
|
- mcaptcha_redis
|
||||||
|
|||||||
@@ -56,23 +56,22 @@ you will be overriding the values set in the configuration files.
|
|||||||
|
|
||||||
### Captcha
|
### Captcha
|
||||||
|
|
||||||
| Name | Value |
|
| Name | Value |
|
||||||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --- |
|
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `MCAPTCHA_captcha_SALT` | Salt has to be long and random |
|
| `MCAPTCHA_captcha_SALT` | Salt has to be long and random |
|
||||||
| `MCAPTCHA_captcha_GC` | Garbage collection duration in seconds, requires tuning but 30 is a good starting point |
|
| `MCAPTCHA_captcha_GC` | Garbage collection duration in seconds, requires tuning but 30 is a good starting point |
|
||||||
| `MCAPTCHA_captcha_RUNNERS` | [Performance] Number of runners to use for PoW validation. Defaults to number of CPUs available |
|
| `MCAPTCHA_captcha_RUNNERS` | [Performance] Number of runners to use for PoW validation. Defaults to number of CPUs available |
|
||||||
| `MCAPTCHA_captcha_QUEUE_LENGTH` | [Performance] PoW Validation queue length, controls how many pending validation jobs can be held in queue |
|
| `MCAPTCHA_captcha_QUEUE_LENGTH` | [Performance] PoW Validation queue length, controls how many pending validation jobs can be held in queue |
|
||||||
| `MCAPTCHA_captcha_ENABLE_STATS` | Record for CAPTCHA events like configuration fetch, solves and authentication of validation token. Useful for commercial deployments. | |
|
| `MCAPTCHA_captcha_ENABLE_STATS` | Record for CAPTCHA events like configuration fetch, solves and authentication of validation token. Useful for commercial deployments. |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time` | This difficulty factor is used in to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for traffic that took the website down |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration`% | Default duration to use in CAPTCHA configuration in easy mode |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time` | This difficulty factor is used in to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||||
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for traffic that took the website down |
|
||||||
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time` | Default time (in seconds) to use to compute difficulty factor using stored PoW performance records. |
|
||||||
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration` | Default duration to use in CAPTCHA configuration in easy mode |
|
||||||
|
|
||||||
\% See commits
|
See commits [`54b14291ec140e`](https://github.com/mCaptcha/mCaptcha/commit/54b14291ec140ea4cbbf73462d3d6fc2d39f2d2c) and [`42544ec421e0`](https://github.com/mCaptcha/mCaptcha/commit/42544ec421e0c3ec4a8d132e6101ab4069bf0065) for more info.
|
||||||
[`54b14291ec140e`](https://github.com/mCaptcha/mCaptcha/commit/54b14291ec140ea4cbbf73462d3d6fc2d39f2d2c)
|
|
||||||
and
|
|
||||||
[`42544ec421e0`](https://github.com/mCaptcha/mCaptcha/commit/42544ec421e0c3ec4a8d132e6101ab4069bf0065)
|
|
||||||
for more info.
|
|
||||||
|
|
||||||
### SMTP
|
### SMTP
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
"webpack-dev-server": "^4.15.1"
|
"webpack-dev-server": "^4.15.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mcaptcha/pow-wasm": "^0.1.0-rc1",
|
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-rc2",
|
||||||
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-rc1",
|
"@mcaptcha/vanilla-glue": "^0.1.0-rc1",
|
||||||
"@mcaptcha/vanilla-glue": "^0.1.0-rc1"
|
"@mcaptcha/pow-wasm": "^0.1.0-rc2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ DUMBSERVE_PASSWORD=$4
|
|||||||
DUMBSERVE_HOST="https://$DUMBSERVE_USERNAME:$DUMBSERVE_PASSWORD@dl.mcaptcha.org"
|
DUMBSERVE_HOST="https://$DUMBSERVE_USERNAME:$DUMBSERVE_PASSWORD@dl.mcaptcha.org"
|
||||||
|
|
||||||
NAME=mcaptcha
|
NAME=mcaptcha
|
||||||
KEY=0CBABF3084E84E867A76709750BE39D10ECE01FB
|
KEY=73DAC973A9ADBB9ADCB5CDC4595A08135BA9FF73
|
||||||
|
|
||||||
TMP_DIR=$(mktemp -d)
|
TMP_DIR=$(mktemp -d)
|
||||||
FILENAME="$NAME-$2-linux-amd64"
|
FILENAME="$NAME-$2-linux-amd64"
|
||||||
@@ -44,6 +44,7 @@ copy() {
|
|||||||
mkdir $TARGET_DIR/docs
|
mkdir $TARGET_DIR/docs
|
||||||
cp docs/DEPLOYMENT.md $TARGET_DIR/docs
|
cp docs/DEPLOYMENT.md $TARGET_DIR/docs
|
||||||
cp docs/CONFIGURATION.md $TARGET_DIR/docs
|
cp docs/CONFIGURATION.md $TARGET_DIR/docs
|
||||||
|
cp config/default.toml $TARGET_DIR/config.toml
|
||||||
|
|
||||||
get_bin
|
get_bin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,79 @@ pub fn calculate(
|
|||||||
Ok(levels)
|
Ok(levels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn calculate_with_percentile(
|
||||||
|
data: &AppData,
|
||||||
|
tp: &TrafficPattern,
|
||||||
|
) -> ServiceResult<Option<Vec<Level>>> {
|
||||||
|
use crate::api::v1::stats::{percentile_bench_runner, PercentileReq};
|
||||||
|
|
||||||
|
let strategy = &data.settings.captcha.default_difficulty_strategy;
|
||||||
|
|
||||||
|
if strategy.avg_traffic_time.is_none()
|
||||||
|
&& strategy.peak_sustainable_traffic_time.is_none()
|
||||||
|
&& strategy.broke_my_site_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut req = PercentileReq {
|
||||||
|
time: strategy.avg_traffic_time.unwrap(),
|
||||||
|
percentile: 90.00,
|
||||||
|
};
|
||||||
|
let resp = percentile_bench_runner(data, &req).await?;
|
||||||
|
if resp.difficulty_factor.is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let avg_traffic_difficulty = resp.difficulty_factor.unwrap();
|
||||||
|
|
||||||
|
req.time = strategy.peak_sustainable_traffic_time.unwrap();
|
||||||
|
let resp = percentile_bench_runner(data, &req).await?;
|
||||||
|
if resp.difficulty_factor.is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let peak_sustainable_traffic_difficulty = resp.difficulty_factor.unwrap();
|
||||||
|
|
||||||
|
req.time = strategy.broke_my_site_traffic_time.unwrap();
|
||||||
|
let resp = percentile_bench_runner(data, &req).await?;
|
||||||
|
let broke_my_site_traffic_difficulty = if resp.difficulty_factor.is_none() {
|
||||||
|
resp.difficulty_factor.unwrap()
|
||||||
|
} else {
|
||||||
|
peak_sustainable_traffic_difficulty * 2
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut levels = vec![
|
||||||
|
LevelBuilder::default()
|
||||||
|
.difficulty_factor(avg_traffic_difficulty)?
|
||||||
|
.visitor_threshold(tp.avg_traffic)
|
||||||
|
.build()?,
|
||||||
|
LevelBuilder::default()
|
||||||
|
.difficulty_factor(peak_sustainable_traffic_difficulty)?
|
||||||
|
.visitor_threshold(tp.peak_sustainable_traffic)
|
||||||
|
.build()?,
|
||||||
|
];
|
||||||
|
let mut highest_level = LevelBuilder::default();
|
||||||
|
highest_level.difficulty_factor(broke_my_site_traffic_difficulty)?;
|
||||||
|
|
||||||
|
match tp.broke_my_site_traffic {
|
||||||
|
Some(broke_my_site_traffic) => {
|
||||||
|
highest_level.visitor_threshold(broke_my_site_traffic)
|
||||||
|
}
|
||||||
|
None => match tp
|
||||||
|
.peak_sustainable_traffic
|
||||||
|
.checked_add(tp.peak_sustainable_traffic / 2)
|
||||||
|
{
|
||||||
|
Some(num) => highest_level.visitor_threshold(num),
|
||||||
|
// TODO check for overflow: database saves these values as i32, so this u32 is cast
|
||||||
|
// into i32. Should choose bigger number or casts properly
|
||||||
|
None => highest_level.visitor_threshold(u32::MAX),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
levels.push(highest_level.build()?);
|
||||||
|
|
||||||
|
Ok(Some(levels))
|
||||||
|
}
|
||||||
|
|
||||||
#[my_codegen::post(
|
#[my_codegen::post(
|
||||||
path = "crate::V1_API_ROUTES.captcha.easy.create",
|
path = "crate::V1_API_ROUTES.captcha.easy.create",
|
||||||
wrap = "crate::api::v1::get_middleware()"
|
wrap = "crate::api::v1::get_middleware()"
|
||||||
@@ -113,8 +186,12 @@ async fn create(
|
|||||||
let username = id.identity().unwrap();
|
let username = id.identity().unwrap();
|
||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
let pattern = (&payload).into();
|
let pattern = (&payload).into();
|
||||||
let levels =
|
let levels = if let Some(levels) = calculate_with_percentile(&data, &pattern).await?
|
||||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
{
|
||||||
|
levels
|
||||||
|
} else {
|
||||||
|
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?
|
||||||
|
};
|
||||||
let msg = CreateCaptcha {
|
let msg = CreateCaptcha {
|
||||||
levels,
|
levels,
|
||||||
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
||||||
@@ -147,6 +224,15 @@ async fn update(
|
|||||||
) -> ServiceResult<impl Responder> {
|
) -> ServiceResult<impl Responder> {
|
||||||
let username = id.identity().unwrap();
|
let username = id.identity().unwrap();
|
||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
|
update_runner(&data, payload, username).await?;
|
||||||
|
Ok(HttpResponse::Ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_runner(
|
||||||
|
data: &AppData,
|
||||||
|
payload: UpdateTrafficPattern,
|
||||||
|
username: String,
|
||||||
|
) -> ServiceResult<()> {
|
||||||
let pattern = (&payload.pattern).into();
|
let pattern = (&payload.pattern).into();
|
||||||
let levels =
|
let levels =
|
||||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
||||||
@@ -167,7 +253,7 @@ async fn update(
|
|||||||
.add_traffic_pattern(&username, &msg.key, &pattern)
|
.add_traffic_pattern(&username, &msg.key, &pattern)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ impl Health {
|
|||||||
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")]
|
#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")]
|
||||||
async fn health(data: AppData) -> impl Responder {
|
async fn health(data: AppData) -> impl Responder {
|
||||||
let mut resp_builder = HealthBuilder::default();
|
let mut resp_builder = HealthBuilder::default();
|
||||||
|
resp_builder.redis(None);
|
||||||
|
|
||||||
resp_builder.db(data.db.ping().await);
|
resp_builder.db(data.db.ping().await);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub mod meta;
|
|||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
pub mod pow;
|
pub mod pow;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
pub mod stats;
|
||||||
pub mod survey;
|
pub mod survey;
|
||||||
|
|
||||||
pub use routes::ROUTES;
|
pub use routes::ROUTES;
|
||||||
@@ -26,6 +27,7 @@ pub fn services(cfg: &mut ServiceConfig) {
|
|||||||
mcaptcha::services(cfg);
|
mcaptcha::services(cfg);
|
||||||
notifications::services(cfg);
|
notifications::services(cfg);
|
||||||
survey::services(cfg);
|
survey::services(cfg);
|
||||||
|
stats::services(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
//use actix::prelude::*;
|
//use actix::prelude::*;
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use libmcaptcha::pow::PoWConfig;
|
||||||
use libmcaptcha::{
|
use libmcaptcha::{
|
||||||
defense::LevelBuilder, master::messages::AddSiteBuilder, DefenseBuilder,
|
defense::LevelBuilder, master::messages::AddSiteBuilder, DefenseBuilder,
|
||||||
MCaptchaBuilder,
|
MCaptchaBuilder,
|
||||||
@@ -21,7 +22,13 @@ pub struct GetConfigPayload {
|
|||||||
pub key: String,
|
pub key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// API keys are mcaptcha actor names
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ApiPoWConfig {
|
||||||
|
pub string: String,
|
||||||
|
pub difficulty_factor: u32,
|
||||||
|
pub salt: String,
|
||||||
|
pub max_recorded_nonce: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// get PoW configuration for an mcaptcha key
|
/// get PoW configuration for an mcaptcha key
|
||||||
#[my_codegen::post(path = "V1_API_ROUTES.pow.get_config()")]
|
#[my_codegen::post(path = "V1_API_ROUTES.pow.get_config()")]
|
||||||
@@ -35,52 +42,34 @@ pub async fn get_config(
|
|||||||
}
|
}
|
||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
|
|
||||||
match data.captcha.get_pow(payload.key.clone()).await {
|
let config: ServiceResult<PoWConfig> =
|
||||||
Ok(Some(config)) => {
|
match data.captcha.get_pow(payload.key.clone()).await {
|
||||||
data.stats.record_fetch(&data, &payload.key).await?;
|
Ok(Some(config)) => Ok(config),
|
||||||
Ok(HttpResponse::Ok().json(config))
|
Ok(None) => {
|
||||||
}
|
init_mcaptcha(&data, &payload.key).await?;
|
||||||
Ok(None) => {
|
let config = data
|
||||||
init_mcaptcha(&data, &payload.key).await?;
|
.captcha
|
||||||
let config = data
|
.get_pow(payload.key.clone())
|
||||||
.captcha
|
.await
|
||||||
.get_pow(payload.key.clone())
|
.expect("mcaptcha should be initialized and ready to go");
|
||||||
.await
|
Ok(config.unwrap())
|
||||||
.expect("mcaptcha should be initialized and ready to go");
|
}
|
||||||
// background it. would require data::Data to be static
|
Err(e) => Err(e.into()),
|
||||||
// to satidfy lifetime
|
};
|
||||||
data.stats.record_fetch(&data, &payload.key).await?;
|
let config = config?;
|
||||||
Ok(HttpResponse::Ok().json(config))
|
let max_nonce = data
|
||||||
}
|
.db
|
||||||
Err(e) => Err(e.into()),
|
.get_max_nonce_for_level(&payload.key, config.difficulty_factor)
|
||||||
}
|
.await?;
|
||||||
|
data.stats.record_fetch(&data, &payload.key).await?;
|
||||||
|
|
||||||
// match res.exists {
|
let config = ApiPoWConfig {
|
||||||
// Some(true) => {
|
string: config.string,
|
||||||
// match data.captcha.get_pow(payload.key.clone()).await {
|
difficulty_factor: config.difficulty_factor,
|
||||||
// Ok(Some(config)) => {
|
salt: config.salt,
|
||||||
// record_fetch(&payload.key, &data.db).await;
|
max_recorded_nonce: max_nonce,
|
||||||
// Ok(HttpResponse::Ok().json(config))
|
};
|
||||||
// }
|
Ok(HttpResponse::Ok().json(config))
|
||||||
// Ok(None) => {
|
|
||||||
// init_mcaptcha(&data, &payload.key).await?;
|
|
||||||
// let config = data
|
|
||||||
// .captcha
|
|
||||||
// .get_pow(payload.key.clone())
|
|
||||||
// .await
|
|
||||||
// .expect("mcaptcha should be initialized and ready to go");
|
|
||||||
// // background it. would require data::Data to be static
|
|
||||||
// // to satidfy lifetime
|
|
||||||
// record_fetch(&payload.key, &data.db).await;
|
|
||||||
// Ok(HttpResponse::Ok().json(config))
|
|
||||||
// }
|
|
||||||
// Err(e) => Err(e.into()),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Some(false) => Err(ServiceError::TokenNotFound),
|
|
||||||
// None => Err(ServiceError::TokenNotFound),
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
/// Call this when [MCaptcha][libmcaptcha::MCaptcha] is not in master.
|
/// Call this when [MCaptcha][libmcaptcha::MCaptcha] is not in master.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ pub async fn verify_pow(
|
|||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
let worker_type = payload.worker_type.clone();
|
let worker_type = payload.worker_type.clone();
|
||||||
let time = payload.time;
|
let time = payload.time;
|
||||||
|
let nonce = payload.nonce;
|
||||||
let (res, difficulty_factor) = data.captcha.verify_pow(payload.into(), ip).await?;
|
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 let (Some(time), Some(worker_type)) = (time, worker_type) {
|
if let (Some(time), Some(worker_type)) = (time, worker_type) {
|
||||||
@@ -75,6 +76,9 @@ pub async fn verify_pow(
|
|||||||
};
|
};
|
||||||
data.db.analysis_save(&key, &analytics).await?;
|
data.db.analysis_save(&key, &analytics).await?;
|
||||||
}
|
}
|
||||||
|
data.db
|
||||||
|
.update_max_nonce_for_level(&key, difficulty_factor, nonce as u32)
|
||||||
|
.await?;
|
||||||
let payload = ValidationToken { token: res };
|
let payload = ValidationToken { token: res };
|
||||||
Ok(HttpResponse::Ok().json(payload))
|
Ok(HttpResponse::Ok().json(payload))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use super::mcaptcha::routes::Captcha;
|
|||||||
use super::meta::routes::Meta;
|
use super::meta::routes::Meta;
|
||||||
use super::notifications::routes::Notifications;
|
use super::notifications::routes::Notifications;
|
||||||
use super::pow::routes::PoW;
|
use super::pow::routes::PoW;
|
||||||
|
use super::stats::routes::Stats;
|
||||||
use super::survey::routes::Survey;
|
use super::survey::routes::Survey;
|
||||||
|
|
||||||
pub const ROUTES: Routes = Routes::new();
|
pub const ROUTES: Routes = Routes::new();
|
||||||
@@ -23,6 +24,7 @@ pub struct Routes {
|
|||||||
pub pow: PoW,
|
pub pow: PoW,
|
||||||
pub survey: Survey,
|
pub survey: Survey,
|
||||||
pub notifications: Notifications,
|
pub notifications: Notifications,
|
||||||
|
pub stats: Stats,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Routes {
|
impl Routes {
|
||||||
@@ -35,6 +37,7 @@ impl Routes {
|
|||||||
pow: PoW::new(),
|
pow: PoW::new(),
|
||||||
notifications: Notifications::new(),
|
notifications: Notifications::new(),
|
||||||
survey: Survey::new(),
|
survey: Survey::new(),
|
||||||
|
stats: Stats::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
259
src/api/v1/stats.rs
Normal file
259
src/api/v1/stats.rs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
// Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::errors::*;
|
||||||
|
use crate::AppData;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||||
|
pub struct BuildDetails {
|
||||||
|
pub version: &'static str,
|
||||||
|
pub git_commit_hash: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod routes {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Stats {
|
||||||
|
pub percentile_benches: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stats {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
percentile_benches: "/api/v1/stats/analytics/percentile",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn percentile_bench_runner(
|
||||||
|
data: &AppData,
|
||||||
|
req: &PercentileReq,
|
||||||
|
) -> ServiceResult<PercentileResp> {
|
||||||
|
let count = data.db.stats_get_num_logs_under_time(req.time).await?;
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return Ok(PercentileResp {
|
||||||
|
difficulty_factor: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < 2 {
|
||||||
|
return Ok(PercentileResp {
|
||||||
|
difficulty_factor: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let location = ((count - 1) as f64 * (req.percentile / 100.00)) + 1.00;
|
||||||
|
let fraction = location - location.floor();
|
||||||
|
|
||||||
|
if fraction > 0.00 {
|
||||||
|
if let (Some(base), Some(ceiling)) = (
|
||||||
|
data.db
|
||||||
|
.stats_get_entry_at_location_for_time_limit_asc(
|
||||||
|
req.time,
|
||||||
|
location.floor() as u32,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
data.db
|
||||||
|
.stats_get_entry_at_location_for_time_limit_asc(
|
||||||
|
req.time,
|
||||||
|
location.floor() as u32 + 1,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
) {
|
||||||
|
let res = base as u32 + ((ceiling - base) as f64 * fraction).floor() as u32;
|
||||||
|
|
||||||
|
return Ok(PercentileResp {
|
||||||
|
difficulty_factor: Some(res),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(base) = data
|
||||||
|
.db
|
||||||
|
.stats_get_entry_at_location_for_time_limit_asc(
|
||||||
|
req.time,
|
||||||
|
location.floor() as u32,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let res = base as u32;
|
||||||
|
|
||||||
|
return Ok(PercentileResp {
|
||||||
|
difficulty_factor: Some(res),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(PercentileResp {
|
||||||
|
difficulty_factor: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get difficulty factor with max time limit for percentile of stats
|
||||||
|
#[my_codegen::post(path = "crate::V1_API_ROUTES.stats.percentile_benches")]
|
||||||
|
async fn percentile_benches(
|
||||||
|
data: AppData,
|
||||||
|
payload: web::Json<PercentileReq>,
|
||||||
|
) -> ServiceResult<impl Responder> {
|
||||||
|
Ok(HttpResponse::Ok().json(percentile_bench_runner(&data, &payload).await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||||
|
/// Health check return datatype
|
||||||
|
pub struct PercentileReq {
|
||||||
|
pub time: u32,
|
||||||
|
pub percentile: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
|
||||||
|
/// Health check return datatype
|
||||||
|
pub struct PercentileResp {
|
||||||
|
pub difficulty_factor: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(percentile_benches);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_web::{http::StatusCode, test, App};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::api::v1::services;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn stats_bench_work_pg() {
|
||||||
|
let data = crate::tests::pg::get_data().await;
|
||||||
|
stats_bench_work(data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn stats_bench_work_maria() {
|
||||||
|
let data = crate::tests::maria::get_data().await;
|
||||||
|
stats_bench_work(data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stats_bench_work(data: ArcData) {
|
||||||
|
use crate::tests::*;
|
||||||
|
|
||||||
|
const NAME: &str = "benchstatsuesr";
|
||||||
|
const EMAIL: &str = "benchstatsuesr@testadminuser.com";
|
||||||
|
const PASSWORD: &str = "longpassword2";
|
||||||
|
|
||||||
|
const DEVICE_USER_PROVIDED: &str = "foo";
|
||||||
|
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
|
||||||
|
const THREADS: i32 = 4;
|
||||||
|
|
||||||
|
let data = &data;
|
||||||
|
{
|
||||||
|
delete_user(&data, NAME).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
register_and_signin(data, NAME, EMAIL, PASSWORD).await;
|
||||||
|
// create captcha
|
||||||
|
let (_, _signin_resp, key) = add_levels_util(data, NAME, PASSWORD).await;
|
||||||
|
let app = get_app!(data).await;
|
||||||
|
|
||||||
|
let page = 1;
|
||||||
|
let tmp_id = uuid::Uuid::new_v4();
|
||||||
|
let download_rotue = V1_API_ROUTES
|
||||||
|
.survey
|
||||||
|
.get_download_route(&tmp_id.to_string(), page);
|
||||||
|
|
||||||
|
let download_req = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::get().uri(&download_rotue).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(download_req.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
data.db
|
||||||
|
.analytics_create_psuedo_id_if_not_exists(&key.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let psuedo_id = data
|
||||||
|
.db
|
||||||
|
.analytics_get_psuedo_id_from_capmaign_id(&key.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for i in 1..6 {
|
||||||
|
println!("[{i}] Saving analytics");
|
||||||
|
let analytics = db_core::CreatePerformanceAnalytics {
|
||||||
|
time: i,
|
||||||
|
difficulty_factor: i,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
};
|
||||||
|
data.db.analysis_save(&key.key, &analytics).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 1,
|
||||||
|
percentile: 99.00,
|
||||||
|
};
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
assert!(resp.difficulty_factor.is_none());
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 1,
|
||||||
|
percentile: 100.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
assert!(resp.difficulty_factor.is_none());
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 2,
|
||||||
|
percentile: 100.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.difficulty_factor.unwrap(), 2);
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 5,
|
||||||
|
percentile: 90.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.difficulty_factor.unwrap(), 4);
|
||||||
|
delete_user(&data, NAME).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/demo.rs
50
src/demo.rs
@@ -8,6 +8,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use actix::clock::sleep;
|
use actix::clock::sleep;
|
||||||
use actix::spawn;
|
use actix::spawn;
|
||||||
|
use tokio::sync::oneshot::{channel, error::TryRecvError, Receiver, Sender};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use crate::api::v1::account::delete::runners::delete_user;
|
use crate::api::v1::account::delete::runners::delete_user;
|
||||||
@@ -23,20 +24,24 @@ pub const DEMO_USER: &str = "aaronsw";
|
|||||||
pub const DEMO_PASSWORD: &str = "password";
|
pub const DEMO_PASSWORD: &str = "password";
|
||||||
|
|
||||||
pub struct DemoUser {
|
pub struct DemoUser {
|
||||||
handle: JoinHandle<()>,
|
tx: Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DemoUser {
|
impl DemoUser {
|
||||||
pub async fn spawn(data: AppData, duration: Duration) -> ServiceResult<Self> {
|
pub async fn spawn(
|
||||||
let handle = Self::run(data, duration).await?;
|
data: AppData,
|
||||||
let d = Self { handle };
|
duration: u32,
|
||||||
|
) -> ServiceResult<(Self, JoinHandle<()>)> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let handle = Self::run(data, duration, rx).await?;
|
||||||
|
let d = Self { tx };
|
||||||
|
|
||||||
Ok(d)
|
Ok((d, handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn abort(&self) {
|
pub fn abort(mut self) {
|
||||||
self.handle.abort();
|
self.tx.send(());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// register demo user runner
|
/// register demo user runner
|
||||||
@@ -71,16 +76,38 @@ impl DemoUser {
|
|||||||
|
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
data: AppData,
|
data: AppData,
|
||||||
duration: Duration,
|
duration: u32,
|
||||||
|
mut rx: Receiver<()>,
|
||||||
) -> ServiceResult<JoinHandle<()>> {
|
) -> ServiceResult<JoinHandle<()>> {
|
||||||
Self::register_demo_user(&data).await?;
|
Self::register_demo_user(&data).await?;
|
||||||
|
|
||||||
|
fn can_run(rx: &mut Receiver<()>) -> bool {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Err(TryRecvError::Empty) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut exit = false;
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
loop {
|
loop {
|
||||||
sleep(duration).await;
|
if exit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for _ in 0..duration {
|
||||||
|
if can_run(&mut rx) {
|
||||||
|
sleep(Duration::new(1, 0)).await;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = Self::delete_demo_user(&data).await {
|
if let Err(e) = Self::delete_demo_user(&data).await {
|
||||||
log::error!("Error while deleting demo user: {:?}", e);
|
log::error!("Error while deleting demo user: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = Self::register_demo_user(&data).await {
|
if let Err(e) = Self::register_demo_user(&data).await {
|
||||||
log::error!("Error while registering demo user: {:?}", e);
|
log::error!("Error while registering demo user: {:?}", e);
|
||||||
}
|
}
|
||||||
@@ -133,7 +160,7 @@ mod tests {
|
|||||||
assert!(!username_exists(&payload, &data).await.unwrap().exists);
|
assert!(!username_exists(&payload, &data).await.unwrap().exists);
|
||||||
|
|
||||||
// test the runner
|
// test the runner
|
||||||
let user = DemoUser::spawn(data, duration).await.unwrap();
|
let user = DemoUser::spawn(data, DURATION as u32).await.unwrap();
|
||||||
let (_, signin_resp, token_key) =
|
let (_, signin_resp, token_key) =
|
||||||
add_levels_util(data_inner, DEMO_USER, DEMO_PASSWORD).await;
|
add_levels_util(data_inner, DEMO_USER, DEMO_PASSWORD).await;
|
||||||
let cookies = get_cookie!(signin_resp);
|
let cookies = get_cookie!(signin_resp);
|
||||||
@@ -162,6 +189,7 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
let res_levels: Vec<Level> = test::read_body_json(resp).await;
|
let res_levels: Vec<Level> = test::read_body_json(resp).await;
|
||||||
assert!(res_levels.is_empty());
|
assert!(res_levels.is_empty());
|
||||||
user.abort();
|
user.0.abort();
|
||||||
|
user.1.await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
src/easy.rs
Normal file
132
src/easy.rs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (C) 2024// Copyright (C) 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
//use std::sync::atomicBool
|
||||||
|
|
||||||
|
use actix::clock::sleep;
|
||||||
|
use actix::spawn;
|
||||||
|
use tokio::sync::oneshot::{channel, error::TryRecvError, Receiver, Sender};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
use crate::api::v1::mcaptcha::easy::{
|
||||||
|
update_runner, TrafficPatternRequest, UpdateTrafficPattern,
|
||||||
|
};
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
use errors::*;
|
||||||
|
|
||||||
|
pub struct UpdateEasyCaptcha {
|
||||||
|
tx: Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateEasyCaptcha {
|
||||||
|
pub async fn spawn(
|
||||||
|
data: AppData,
|
||||||
|
duration: u32,
|
||||||
|
) -> ServiceResult<(Self, JoinHandle<()>)> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let handle = Self::run(data, duration, rx).await?;
|
||||||
|
let d = Self { tx };
|
||||||
|
|
||||||
|
Ok((d, handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn abort(mut self) {
|
||||||
|
self.tx.send(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update configurations
|
||||||
|
async fn update_captcha_configurations(
|
||||||
|
data: &AppData,
|
||||||
|
rx: &mut Receiver<()>,
|
||||||
|
) -> ServiceResult<()> {
|
||||||
|
let limit = 10;
|
||||||
|
let mut offset = 0;
|
||||||
|
let mut page = 0;
|
||||||
|
loop {
|
||||||
|
offset = page * limit;
|
||||||
|
|
||||||
|
if !Self::can_run(rx) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut patterns = data.db.get_all_easy_captchas(limit, offset).await?;
|
||||||
|
for pattern in patterns.drain(0..) {
|
||||||
|
if !Self::can_run(rx) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let publish_benchmarks =
|
||||||
|
data.db.analytics_captcha_is_published(&pattern.key).await?;
|
||||||
|
|
||||||
|
let req = UpdateTrafficPattern {
|
||||||
|
pattern: TrafficPatternRequest {
|
||||||
|
avg_traffic: pattern.traffic_pattern.avg_traffic,
|
||||||
|
peak_sustainable_traffic: pattern
|
||||||
|
.traffic_pattern
|
||||||
|
.peak_sustainable_traffic,
|
||||||
|
broke_my_site_traffic: pattern
|
||||||
|
.traffic_pattern
|
||||||
|
.broke_my_site_traffic,
|
||||||
|
description: pattern.description,
|
||||||
|
publish_benchmarks,
|
||||||
|
},
|
||||||
|
key: pattern.key,
|
||||||
|
};
|
||||||
|
if !Self::can_run(rx) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
update_runner(&data, req, pattern.username).await?;
|
||||||
|
}
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_run(rx: &mut Receiver<()>) -> bool {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Err(TryRecvError::Empty) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
data: AppData,
|
||||||
|
duration: u32,
|
||||||
|
mut rx: Receiver<()>,
|
||||||
|
) -> ServiceResult<JoinHandle<()>> {
|
||||||
|
let mut exit = false;
|
||||||
|
let fut = async move {
|
||||||
|
loop {
|
||||||
|
if exit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for _ in 0..duration {
|
||||||
|
if Self::can_run(&mut rx) {
|
||||||
|
sleep(Duration::new(1, 0)).await;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = Self::update_captcha_configurations(&data, &mut rx)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
{
|
||||||
|
log::error!(
|
||||||
|
"Tried to update easy captcha configurations in background {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let handle = spawn(fut);
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main.rs
27
src/main.rs
@@ -14,6 +14,7 @@ use actix_web::{
|
|||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod data;
|
mod data;
|
||||||
@@ -21,6 +22,7 @@ mod date;
|
|||||||
mod db;
|
mod db;
|
||||||
mod demo;
|
mod demo;
|
||||||
mod docs;
|
mod docs;
|
||||||
|
mod easy;
|
||||||
mod email;
|
mod email;
|
||||||
mod errors;
|
mod errors;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -110,11 +112,22 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let data = Data::new(&settings, secrets.clone()).await;
|
let data = Data::new(&settings, secrets.clone()).await;
|
||||||
let data = actix_web::web::Data::new(data);
|
let data = actix_web::web::Data::new(data);
|
||||||
|
|
||||||
let mut demo_user: Option<DemoUser> = None;
|
let mut demo_user: Option<(DemoUser, JoinHandle<()>)> = None;
|
||||||
|
|
||||||
if settings.allow_demo && settings.allow_registration {
|
if settings.allow_demo && settings.allow_registration {
|
||||||
demo_user = Some(
|
demo_user = Some(DemoUser::spawn(data.clone(), 60 * 30).await.unwrap());
|
||||||
DemoUser::spawn(data.clone(), Duration::from_secs(60 * 30))
|
}
|
||||||
|
|
||||||
|
let mut update_easy_captcha: Option<(easy::UpdateEasyCaptcha, JoinHandle<()>)> =
|
||||||
|
None;
|
||||||
|
if settings
|
||||||
|
.captcha
|
||||||
|
.default_difficulty_strategy
|
||||||
|
.avg_traffic_time
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
update_easy_captcha = Some(
|
||||||
|
easy::UpdateEasyCaptcha::spawn(data.clone(), 60 * 30)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
@@ -156,7 +169,13 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(demo_user) = demo_user {
|
if let Some(demo_user) = demo_user {
|
||||||
demo_user.abort();
|
demo_user.0.abort();
|
||||||
|
demo_user.1.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(update_easy_captcha) = update_easy_captcha {
|
||||||
|
update_easy_captcha.0.abort();
|
||||||
|
update_easy_captcha.1.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(survey_upload_handle) = survey_upload_handle {
|
if let Some(survey_upload_handle) = survey_upload_handle {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use sailfish::TemplateOnce;
|
|||||||
mod notifications;
|
mod notifications;
|
||||||
mod settings;
|
mod settings;
|
||||||
pub mod sitekey;
|
pub mod sitekey;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use db_core::Captcha;
|
use db_core::Captcha;
|
||||||
|
|
||||||
@@ -47,18 +48,21 @@ pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
|||||||
cfg.service(panel);
|
cfg.service(panel);
|
||||||
settings::services(cfg);
|
settings::services(cfg);
|
||||||
sitekey::services(cfg);
|
sitekey::services(cfg);
|
||||||
|
utils::services(cfg);
|
||||||
cfg.service(notifications::notifications);
|
cfg.service(notifications::notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod routes {
|
pub mod routes {
|
||||||
use super::settings::routes::Settings;
|
use super::settings::routes::Settings;
|
||||||
use super::sitekey::routes::Sitekey;
|
use super::sitekey::routes::Sitekey;
|
||||||
|
use super::utils::routes::Utils;
|
||||||
|
|
||||||
pub struct Panel {
|
pub struct Panel {
|
||||||
pub home: &'static str,
|
pub home: &'static str,
|
||||||
pub sitekey: Sitekey,
|
pub sitekey: Sitekey,
|
||||||
pub notifications: &'static str,
|
pub notifications: &'static str,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
|
pub utils: Utils,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel {
|
impl Panel {
|
||||||
@@ -68,10 +72,11 @@ pub mod routes {
|
|||||||
sitekey: Sitekey::new(),
|
sitekey: Sitekey::new(),
|
||||||
notifications: "/notifications",
|
notifications: "/notifications",
|
||||||
settings: Settings::new(),
|
settings: Settings::new(),
|
||||||
|
utils: Utils::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn get_sitemap() -> [&'static str; 5] {
|
pub const fn get_sitemap() -> [&'static str; 6] {
|
||||||
const PANEL: Panel = Panel::new();
|
const PANEL: Panel = Panel::new();
|
||||||
const S: [&str; 2] = Sitekey::get_sitemap();
|
const S: [&str; 2] = Sitekey::get_sitemap();
|
||||||
|
|
||||||
@@ -81,6 +86,7 @@ pub mod routes {
|
|||||||
S[0],
|
S[0],
|
||||||
S[1],
|
S[1],
|
||||||
Settings::get_sitemap()[0],
|
Settings::get_sitemap()[0],
|
||||||
|
Utils::get_sitemap()[0],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
280
src/pages/panel/utils.rs
Normal file
280
src/pages/panel/utils.rs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
// Copyright (C) 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use actix_identity::Identity;
|
||||||
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use sailfish::TemplateOnce;
|
||||||
|
|
||||||
|
use crate::api::v1::stats::{percentile_bench_runner, PercentileReq, PercentileResp};
|
||||||
|
use crate::errors::PageResult;
|
||||||
|
use crate::pages::auth::sudo::SudoPage;
|
||||||
|
use crate::AppData;
|
||||||
|
|
||||||
|
pub mod routes {
|
||||||
|
pub struct Utils {
|
||||||
|
pub percentile: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Utils {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Utils {
|
||||||
|
percentile: "/utils/percentile",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_sitemap() -> [&'static str; 1] {
|
||||||
|
const S: Utils = Utils::new();
|
||||||
|
[S.percentile]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
||||||
|
cfg.service(get_percentile);
|
||||||
|
cfg.service(post_percentile);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAGE: &str = "Difficulty factor statistics";
|
||||||
|
|
||||||
|
#[derive(TemplateOnce, Clone)]
|
||||||
|
#[template(path = "panel/utils/percentile/index.html")]
|
||||||
|
pub struct PercentilePage {
|
||||||
|
time: Option<u32>,
|
||||||
|
percentile: Option<f64>,
|
||||||
|
difficulty_factor: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[my_codegen::get(
|
||||||
|
path = "crate::PAGES.panel.utils.percentile",
|
||||||
|
wrap = "crate::pages::get_middleware()"
|
||||||
|
)]
|
||||||
|
async fn get_percentile(id: Identity) -> PageResult<impl Responder> {
|
||||||
|
let data = PercentilePage {
|
||||||
|
time: None,
|
||||||
|
percentile: None,
|
||||||
|
difficulty_factor: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = data.render_once().unwrap();
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[my_codegen::post(
|
||||||
|
path = "crate::PAGES.panel.utils.percentile",
|
||||||
|
wrap = "crate::pages::get_middleware()"
|
||||||
|
)]
|
||||||
|
async fn post_percentile(
|
||||||
|
data: AppData,
|
||||||
|
id: Identity,
|
||||||
|
payload: web::Form<PercentileReq>,
|
||||||
|
) -> PageResult<impl Responder> {
|
||||||
|
let resp = percentile_bench_runner(&data, &payload).await?;
|
||||||
|
let page = PercentilePage {
|
||||||
|
time: Some(payload.time),
|
||||||
|
percentile: Some(payload.percentile),
|
||||||
|
difficulty_factor: resp.difficulty_factor,
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = page.render_once().unwrap();
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix_web::{http::StatusCode, test, web::Bytes, App};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::api::v1::services;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn page_stats_bench_work_pg() {
|
||||||
|
let data = crate::tests::pg::get_data().await;
|
||||||
|
page_stats_bench_work(data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn page_stats_bench_work_maria() {
|
||||||
|
let data = crate::tests::maria::get_data().await;
|
||||||
|
page_stats_bench_work(data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn page_stats_bench_work(data: ArcData) {
|
||||||
|
use crate::tests::*;
|
||||||
|
|
||||||
|
const NAME: &str = "pagebenchstatsuesr";
|
||||||
|
const EMAIL: &str = "pagebenchstatsuesr@testadminuser.com";
|
||||||
|
const PASSWORD: &str = "longpassword2";
|
||||||
|
|
||||||
|
const DEVICE_USER_PROVIDED: &str = "foo";
|
||||||
|
const DEVICE_SOFTWARE_RECOGNISED: &str = "Foobar.v2";
|
||||||
|
const THREADS: i32 = 4;
|
||||||
|
|
||||||
|
let data = &data;
|
||||||
|
{
|
||||||
|
delete_user(&data, NAME).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
register_and_signin(data, NAME, EMAIL, PASSWORD).await;
|
||||||
|
// create captcha
|
||||||
|
let (_, signin_resp, key) = add_levels_util(data, NAME, PASSWORD).await;
|
||||||
|
let app = get_app!(data).await;
|
||||||
|
let cookies = get_cookie!(signin_resp);
|
||||||
|
|
||||||
|
let page = 1;
|
||||||
|
let tmp_id = uuid::Uuid::new_v4();
|
||||||
|
let download_rotue = V1_API_ROUTES
|
||||||
|
.survey
|
||||||
|
.get_download_route(&tmp_id.to_string(), page);
|
||||||
|
|
||||||
|
let download_req = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::get().uri(&download_rotue).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(download_req.status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
data.db
|
||||||
|
.analytics_create_psuedo_id_if_not_exists(&key.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let psuedo_id = data
|
||||||
|
.db
|
||||||
|
.analytics_get_psuedo_id_from_capmaign_id(&key.key)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for i in 1..6 {
|
||||||
|
println!("[{i}] Saving analytics");
|
||||||
|
let analytics = db_core::CreatePerformanceAnalytics {
|
||||||
|
time: i,
|
||||||
|
difficulty_factor: i,
|
||||||
|
worker_type: "wasm".into(),
|
||||||
|
};
|
||||||
|
data.db.analysis_save(&key.key, &analytics).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 1,
|
||||||
|
percentile: 99.00,
|
||||||
|
};
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
assert!(resp.difficulty_factor.is_none());
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 1,
|
||||||
|
percentile: 100.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
post_request!(&msg, V1_API_ROUTES.stats.percentile_benches).to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
let resp: PercentileResp = test::read_body_json(resp).await;
|
||||||
|
|
||||||
|
// start
|
||||||
|
let percentile_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::get()
|
||||||
|
.uri(&crate::PAGES.panel.utils.percentile)
|
||||||
|
.cookie(cookies.clone())
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let body: Bytes = test::read_body(percentile_resp).await;
|
||||||
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
|
||||||
|
assert!(body.contains("Maximum time taken"));
|
||||||
|
|
||||||
|
let percentile_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::get()
|
||||||
|
.uri(&crate::PAGES.panel.utils.percentile)
|
||||||
|
.cookie(cookies.clone())
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let body: Bytes = test::read_body(percentile_resp).await;
|
||||||
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
|
||||||
|
assert!(body.contains("Maximum time taken"));
|
||||||
|
|
||||||
|
// end
|
||||||
|
// start post
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 1,
|
||||||
|
percentile: 99.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let percentile_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::post()
|
||||||
|
.uri(&crate::PAGES.panel.utils.percentile)
|
||||||
|
.set_form(&msg)
|
||||||
|
.cookie(cookies.clone())
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let body: Bytes = test::read_body(percentile_resp).await;
|
||||||
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
|
||||||
|
assert!(body.contains(
|
||||||
|
"Not enough inputs to compute statistics. Please try again later"
|
||||||
|
));
|
||||||
|
assert!(body.contains(&1.to_string()));
|
||||||
|
assert!(body.contains(&99.00.to_string()));
|
||||||
|
// end post
|
||||||
|
|
||||||
|
// start post
|
||||||
|
|
||||||
|
let msg = PercentileReq {
|
||||||
|
time: 2,
|
||||||
|
percentile: 100.00,
|
||||||
|
};
|
||||||
|
|
||||||
|
let percentile_resp = test::call_service(
|
||||||
|
&app,
|
||||||
|
test::TestRequest::post()
|
||||||
|
.uri(&crate::PAGES.panel.utils.percentile)
|
||||||
|
.set_form(&msg)
|
||||||
|
.cookie(cookies.clone())
|
||||||
|
.to_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(percentile_resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let body: Bytes = test::read_body(percentile_resp).await;
|
||||||
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
|
||||||
|
assert!(body.contains("Difficulty factor: 2"));
|
||||||
|
assert!(body.contains(&2.to_string()));
|
||||||
|
assert!(body.contains(&100.00.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,8 +37,11 @@ pub struct Captcha {
|
|||||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||||
pub struct DefaultDifficultyStrategy {
|
pub struct DefaultDifficultyStrategy {
|
||||||
pub avg_traffic_difficulty: u32,
|
pub avg_traffic_difficulty: u32,
|
||||||
pub broke_my_site_traffic_difficulty: u32,
|
pub avg_traffic_time: Option<u32>,
|
||||||
pub peak_sustainable_traffic_difficulty: u32,
|
pub peak_sustainable_traffic_difficulty: u32,
|
||||||
|
pub peak_sustainable_traffic_time: Option<u32>,
|
||||||
|
pub broke_my_site_traffic_time: Option<u32>,
|
||||||
|
pub broke_my_site_traffic_difficulty: u32,
|
||||||
pub duration: u32,
|
pub duration: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +116,7 @@ pub struct Settings {
|
|||||||
pub smtp: Option<Smtp>,
|
pub smtp: Option<Smtp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
const ENV_VAR_CONFIG: [(&str, &str); 32] = [
|
||||||
/* top-level */
|
/* top-level */
|
||||||
("debug", "MCAPTCHA_debug"),
|
("debug", "MCAPTCHA_debug"),
|
||||||
("commercial", "MCAPTCHA_commercial"),
|
("commercial", "MCAPTCHA_commercial"),
|
||||||
@@ -150,6 +153,9 @@ const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
|||||||
( "captcha.default_difficulty_strategy.duration",
|
( "captcha.default_difficulty_strategy.duration",
|
||||||
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
|
||||||
),
|
),
|
||||||
|
("captcha.default_difficulty_strategy.avg_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time"),
|
||||||
|
("captcha.default_difficulty_strategy.peak_sustainable_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time"),
|
||||||
|
("captcha.default_difficulty_strategy.broke_my_site_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time"),
|
||||||
|
|
||||||
|
|
||||||
/* SMTP */
|
/* SMTP */
|
||||||
@@ -251,6 +257,28 @@ impl Settings {
|
|||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
}
|
}
|
||||||
|
fn check_easy_captcha_config(&self) {
|
||||||
|
let s = &self.captcha.default_difficulty_strategy;
|
||||||
|
if s.avg_traffic_time.is_some() {
|
||||||
|
if s.broke_my_site_traffic_time.is_none()
|
||||||
|
|| s.peak_sustainable_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
panic!("if captcha.default_difficulty_strategy.avg_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.peak_sustainable_traffic_time must also be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.peak_sustainable_traffic_time.is_some() {
|
||||||
|
if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
panic!("if captcha.default_difficulty_strategy.peak_sustainable_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.broke_my_site_traffic_time.is_some() {
|
||||||
|
if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
panic!("if captcha.default_difficulty_strategy.broke_my_site_traffic_time is set, then captcha.default_difficulty_strategy.peak_sustainable_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||||
for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
|
for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
|
||||||
@@ -538,6 +566,30 @@ mod tests {
|
|||||||
999,
|
999,
|
||||||
captcha.default_difficulty_strategy.duration
|
captcha.default_difficulty_strategy.duration
|
||||||
);
|
);
|
||||||
|
helper!(
|
||||||
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time",
|
||||||
|
"10",
|
||||||
|
Some(10),
|
||||||
|
captcha.default_difficulty_strategy.avg_traffic_time
|
||||||
|
);
|
||||||
|
|
||||||
|
helper!(
|
||||||
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time",
|
||||||
|
"20",
|
||||||
|
Some(20),
|
||||||
|
captcha
|
||||||
|
.default_difficulty_strategy
|
||||||
|
.peak_sustainable_traffic_time
|
||||||
|
);
|
||||||
|
|
||||||
|
helper!(
|
||||||
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time",
|
||||||
|
"30",
|
||||||
|
Some(30),
|
||||||
|
captcha
|
||||||
|
.default_difficulty_strategy
|
||||||
|
.broke_my_site_traffic_time
|
||||||
|
);
|
||||||
|
|
||||||
/* SMTP */
|
/* SMTP */
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ pub fn get_settings() -> Settings {
|
|||||||
pub mod pg {
|
pub mod pg {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use sqlx::migrate::MigrateDatabase;
|
||||||
|
|
||||||
|
use crate::api::v1::mcaptcha::get_random;
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
use crate::settings::*;
|
use crate::settings::*;
|
||||||
use crate::survey::SecretsStore;
|
use crate::survey::SecretsStore;
|
||||||
@@ -38,6 +41,16 @@ 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 parsed = url::Url::parse(&url).unwrap();
|
||||||
|
parsed.set_path(&get_random(16));
|
||||||
|
let url = parsed.to_string();
|
||||||
|
|
||||||
|
if sqlx::Postgres::database_exists(&url).await.unwrap() {
|
||||||
|
sqlx::Postgres::drop_database(&url).await.unwrap();
|
||||||
|
}
|
||||||
|
sqlx::Postgres::create_database(&url).await.unwrap();
|
||||||
|
|
||||||
let mut settings = get_settings();
|
let mut settings = get_settings();
|
||||||
settings.captcha.runners = Some(1);
|
settings.captcha.runners = Some(1);
|
||||||
settings.database.url = url.clone();
|
settings.database.url = url.clone();
|
||||||
@@ -50,6 +63,9 @@ pub mod pg {
|
|||||||
pub mod maria {
|
pub mod maria {
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
use sqlx::migrate::MigrateDatabase;
|
||||||
|
|
||||||
|
use crate::api::v1::mcaptcha::get_random;
|
||||||
use crate::data::Data;
|
use crate::data::Data;
|
||||||
use crate::settings::*;
|
use crate::settings::*;
|
||||||
use crate::survey::SecretsStore;
|
use crate::survey::SecretsStore;
|
||||||
@@ -59,6 +75,16 @@ 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 parsed = url::Url::parse(&url).unwrap();
|
||||||
|
parsed.set_path(&get_random(16));
|
||||||
|
let url = parsed.to_string();
|
||||||
|
|
||||||
|
if sqlx::MySql::database_exists(&url).await.unwrap() {
|
||||||
|
sqlx::MySql::drop_database(&url).await.unwrap();
|
||||||
|
}
|
||||||
|
sqlx::MySql::create_database(&url).await.unwrap();
|
||||||
|
|
||||||
let mut settings = get_settings();
|
let mut settings = get_settings();
|
||||||
settings.captcha.runners = Some(1);
|
settings.captcha.runners = Some(1);
|
||||||
settings.database.url = url.clone();
|
settings.database.url = url.clone();
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<nav class="secondary-menu">
|
<nav class="secondary-menu">
|
||||||
<input type="checkbox" class="nav-toggle" id="nav-toggle" >
|
<input type="checkbox" class="nav-toggle" id="nav-toggle" >
|
||||||
<div class="secondary-menu__heading">
|
<div class="secondary-menu__heading">
|
||||||
<a class="novisit" href="/">
|
<a class="novisit" href="/">
|
||||||
<img class="secondary-menu__logo" src="<.= crate::MCAPTCHA_TRANS_ICON.0 .>" alt="<.= crate::MCAPTCHA_TRANS_ICON.1 .>" />
|
<img class="secondary-menu__logo" src="<.= crate::MCAPTCHA_TRANS_ICON.0 .>" alt="<.= crate::MCAPTCHA_TRANS_ICON.1 .>" />
|
||||||
</a>
|
</a>
|
||||||
<a href="/" class="secondary-menu__brand-name">
|
<a href="/" class="secondary-menu__brand-name">
|
||||||
mCaptcha
|
mCaptcha
|
||||||
</a>
|
</a>
|
||||||
<label class="nav__hamburger-menu"for="nav-toggle">
|
<label class="nav__hamburger-menu"for="nav-toggle">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
<ul class="secondary-menu__list">
|
<ul class="secondary-menu__list">
|
||||||
<li class="secondary-menu__item">
|
<li class="secondary-menu__item">
|
||||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.home .>">
|
<a class="secondary-menu__item-link" href="<.= crate::PAGES.home .>">
|
||||||
<img class="secondary-menu__icon" src="<.= crate::HOME.0 .>" alt="<.= crate::HOME.1 .>" />
|
<img class="secondary-menu__icon" src="<.= crate::HOME.0 .>" alt="<.= crate::HOME.1 .>" />
|
||||||
<div class="secondary-menu__item-name">
|
<div class="secondary-menu__item-name">
|
||||||
Overview
|
Overview
|
||||||
@@ -29,13 +29,21 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="secondary-menu__item">
|
<li class="secondary-menu__item">
|
||||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.sitekey.list .>">
|
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.sitekey.list .>">
|
||||||
<img class="secondary-menu__icon" src="<.= crate::KEY.0 .>" alt="<.= crate::KEY.1 .>" />
|
<img class="secondary-menu__icon" src="<.= crate::KEY.0 .>" alt="<.= crate::KEY.1 .>" />
|
||||||
<div class="secondary-menu__item-name">
|
<div class="secondary-menu__item-name">
|
||||||
Site Keys
|
Site Keys
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="secondary-menu__item">
|
||||||
|
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.utils.percentile .>">
|
||||||
|
<img class="secondary-menu__icon" src="<.= crate::BAR_CHART.0 .>" alt="<.= crate::BAR_CHART.1 .>" />
|
||||||
|
<div class="secondary-menu__item-name">
|
||||||
|
Statistics
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="secondary-menu__item">
|
<li class="secondary-menu__item">
|
||||||
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.settings.home .>">
|
<a class="secondary-menu__item-link" href="<.= crate::PAGES.panel.settings.home .>">
|
||||||
<img class="secondary-menu__icon" src="<.= crate::SETTINGS_ICON.0 .>" alt="<.= crate::SETTINGS_ICON.1 .>" />
|
<img class="secondary-menu__icon" src="<.= crate::SETTINGS_ICON.0 .>" alt="<.= crate::SETTINGS_ICON.1 .>" />
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
<. } .>
|
<. } .>
|
||||||
|
|
||||||
<label class="sitekey-form__label" for="publish_benchmarks">
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
Anonymously publish CAPTCHA performance statistics to help other webmasters. <a href="https://mcaptcha.org/blog/introducing-mcaptcha-net">Please see here for more info</a>.
|
||||||
<input
|
<input
|
||||||
class="sitekey-form__input"
|
class="sitekey-form__input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
|||||||
|
|
||||||
|
|
||||||
<label class="sitekey-form__label" for="publish_benchmarks">
|
<label class="sitekey-form__label" for="publish_benchmarks">
|
||||||
Anonymously publish CAPTCHA performance statistics to help other webmasters
|
Anonymously publish CAPTCHA performance statistics to help other webmasters. <a href="https://mcaptcha.org/blog/introducing-mcaptcha-net">Please see here for more info</a>.
|
||||||
<input
|
<input
|
||||||
class="sitekey-form__input"
|
class="sitekey-form__input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
67
templates/panel/utils/percentile/index.html
Normal file
67
templates/panel/utils/percentile/index.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-->
|
||||||
|
|
||||||
|
<. include!("../../../components/headers/index.html"); .>
|
||||||
|
<. include!("../../navbar/index.html"); .>
|
||||||
|
<div class="tmp-layout">
|
||||||
|
<. include!("../../header/index.html"); .>
|
||||||
|
<main class="panel-main">
|
||||||
|
<. include!("../../help-banner/index.html"); .>
|
||||||
|
<!-- Main content container -->
|
||||||
|
<div class="inner-container">
|
||||||
|
<div class="sitekey-form" action="<.= crate::V1_API_ROUTES.captcha.create .>" method="post">
|
||||||
|
<h1 class="form__title">
|
||||||
|
<.= PAGE .>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<form class="settings__form" id="utils_percentile-form"
|
||||||
|
action="<.= crate::PAGES.panel.utils.percentile .>" method="post">
|
||||||
|
|
||||||
|
<. if let Some(difficulty_factor) = difficulty_factor { .>
|
||||||
|
<legend class="sitekey__level-title">
|
||||||
|
<p>Difficulty factor: <.= difficulty_factor .></p>
|
||||||
|
</legend>
|
||||||
|
<. } else { .>
|
||||||
|
<. if time.is_some() && percentile.is_some() { .>
|
||||||
|
<legend class="sitekey__level-title">
|
||||||
|
<p>Not enough inputs to compute statistics. Please try again later</p>
|
||||||
|
</legend>
|
||||||
|
<. } .>
|
||||||
|
<. } .>
|
||||||
|
|
||||||
|
|
||||||
|
<label class="settings-form__label" for="time">
|
||||||
|
Maximum time taken to solve CAPTCHA (in seconds)
|
||||||
|
<input
|
||||||
|
class="settings-form__input"
|
||||||
|
type="number"
|
||||||
|
name="time"
|
||||||
|
required
|
||||||
|
id="time"
|
||||||
|
<. if let Some(time) = time { .>
|
||||||
|
value="<.= time .>"
|
||||||
|
<. } .>
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="settings-form__label" for="percentile">
|
||||||
|
Percentile of requests coming under time limit
|
||||||
|
<input
|
||||||
|
class="settings-form__input"
|
||||||
|
type="number"
|
||||||
|
name="percentile"
|
||||||
|
required
|
||||||
|
id="percentile"
|
||||||
|
<. if let Some(percentile) = percentile { .>
|
||||||
|
value="<.= percentile .>"
|
||||||
|
<. } .>
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<button class="settings__submit-btn" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end of container -->
|
||||||
|
<. include!("../../../components/footers.html"); .>
|
||||||
@@ -56,53 +56,41 @@ type messageTextReturn = {
|
|||||||
error: () => void;
|
error: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const messageText = (): messageTextReturn => {
|
export const BEFORE = "I'm not a robot";
|
||||||
const beforeID = "widget__verification-text--before";
|
export const DURING = "Processing...";
|
||||||
const duringID = "widget__verification-text--during";
|
export const AFTER = "Verified!";
|
||||||
const errorID = "widget__verification-text--error";
|
export const ERROR = "Something went wrong";
|
||||||
const afterID = "widget__verification-text--after";
|
|
||||||
|
|
||||||
const before = new LazyElement(beforeID);
|
export const messageText = (): messageTextReturn => {
|
||||||
const after = new LazyElement(afterID);
|
const conatinerID = "widget__verification-text";
|
||||||
const during = new LazyElement(duringID);
|
|
||||||
const error = new LazyElement(errorID);
|
const container = new LazyElement(conatinerID);
|
||||||
|
|
||||||
/** runner fn to display HTMLElement **/
|
/** runner fn to display HTMLElement **/
|
||||||
const showMsg = (e: HTMLElement) => (e.style.display = "block");
|
const showMsg = (value: string) => {
|
||||||
/** runner fn to hide HTMLElement **/
|
container.get().innerText = value;
|
||||||
const hideMsg = (e: HTMLElement) => (e.style.display = "none");
|
btn().ariaValueText = value;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/** display "before" message **/
|
/** display "before" message **/
|
||||||
before: () => {
|
before: () => {
|
||||||
showMsg(before.get());
|
showMsg(BEFORE);
|
||||||
hideMsg(after.get());
|
|
||||||
hideMsg(during.get());
|
|
||||||
hideMsg(error.get());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** display "after" message **/
|
/** display "after" message **/
|
||||||
after: () => {
|
after: () => {
|
||||||
hideMsg(before.get());
|
showMsg(AFTER);
|
||||||
showMsg(after.get());
|
|
||||||
hideMsg(during.get());
|
|
||||||
hideMsg(error.get());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** display "during" message **/
|
/** display "during" message **/
|
||||||
during: () => {
|
during: () => {
|
||||||
hideMsg(before.get());
|
showMsg(DURING);
|
||||||
hideMsg(after.get());
|
|
||||||
showMsg(during.get());
|
|
||||||
hideMsg(error.get());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** display "error" message **/
|
/** display "error" message **/
|
||||||
error: () => {
|
error: () => {
|
||||||
hideMsg(before.get());
|
showMsg(ERROR);
|
||||||
hideMsg(after.get());
|
|
||||||
hideMsg(during.get());
|
|
||||||
showMsg(error.get());
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,51 +6,57 @@ SPDX-License-Identifier: MIT OR Apache-2.0
|
|||||||
|
|
||||||
<. include!("../components/headers/widget-headers.html"); .>
|
<. include!("../components/headers/widget-headers.html"); .>
|
||||||
<body>
|
<body>
|
||||||
<form class="widget__contaienr">
|
<main class="widget__container">
|
||||||
<noscript>
|
<form class="widget__inner-container">
|
||||||
<div class="widget__noscript-container">
|
<noscript>
|
||||||
<span class="widget__noscript-warning">
|
<div class="widget__noscript-container">
|
||||||
Please enable JavaScript to receive mCaptcha challenge
|
<span class="widget__noscript-warning">
|
||||||
</span>
|
Please enable JavaScript to receive mCaptcha challenge
|
||||||
<a class="widget__source-code" href="https://github.com/mCaptcha">
|
</span>
|
||||||
Read our source code
|
<a class="widget__source-code" href="https://github.com/mCaptcha">
|
||||||
</a>
|
Read our source code
|
||||||
</div>
|
</a>
|
||||||
</noscript>
|
</div>
|
||||||
<label class="widget__verification-container" for="widget__verification-checkbox">
|
</noscript>
|
||||||
<input
|
<label class="widget__verification-container" for="widget__verification-checkbox">
|
||||||
id="widget__verification-checkbox"
|
<span id="widget__verification-text"
|
||||||
class="widget__verification-checkbox"
|
>I'm not a robot</span>
|
||||||
type="checkbox" />
|
<input disabled
|
||||||
<span id="widget__verification-text--before">I'm not a robot</span>
|
id="widget__verification-checkbox"
|
||||||
<span id="widget__verification-text--during">Processing...</span>
|
aria-valuenow="I'm not a robot"
|
||||||
<span id="widget__verification-text--after">Verified!</span>
|
aria-checked="false"
|
||||||
<span id="widget__verification-text--error">Something went wrong</span>
|
role="checkbox"
|
||||||
</label>
|
class="widget__verification-checkbox"
|
||||||
<div class="widget__mcaptcha-details">
|
type="checkbox" />
|
||||||
<a href="<.= crate::PKG_HOMEPAGE .>"
|
</label>
|
||||||
class="widget__mcaptcha-logo-container"
|
<div class="widget__mcaptcha-details">
|
||||||
target="_blank"
|
<a href="<.= crate::PKG_HOMEPAGE .>"
|
||||||
>
|
class="widget__mcaptcha-logo-container"
|
||||||
<img
|
target="_blank"
|
||||||
class="widget__mcaptcha-logo"
|
>
|
||||||
src="<.= crate::FILES.get("./static/cache/img/icon-trans.png").unwrap().>"
|
<img
|
||||||
alt="mCaptcha logo"
|
class="widget__mcaptcha-logo"
|
||||||
/>
|
src="<.= crate::FILES.get("./static/cache/img/icon-trans.png").unwrap().>"
|
||||||
<p class="widget__mcaptcha-brand-name">mCaptcha</p>
|
alt="mCaptcha logo"
|
||||||
</a>
|
/>
|
||||||
<div class="widget__mcaptcha-info-container">
|
<p class="widget__mcaptcha-brand-name">mCaptcha</p>
|
||||||
<a class="widget__mcaptcha-info-link"
|
|
||||||
target="_blank"
|
|
||||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
|
|
||||||
Privacy
|
|
||||||
</a>
|
|
||||||
<a class="widget__mcaptcha-info-link"
|
|
||||||
target="_blank"
|
|
||||||
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
|
|
||||||
Terms
|
|
||||||
</a>
|
</a>
|
||||||
|
<div class="widget__mcaptcha-info-container">
|
||||||
|
<a class="widget__mcaptcha-info-link"
|
||||||
|
target="_blank"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.privacy .>">
|
||||||
|
Privacy
|
||||||
|
</a>
|
||||||
|
<a class="widget__mcaptcha-info-link"
|
||||||
|
target="_blank"
|
||||||
|
href="<.= crate::PKG_HOMEPAGE .><.= crate::PAGES.security .>">
|
||||||
|
Terms
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
<div class="progress__bar"><div
|
||||||
|
aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"
|
||||||
|
role="progressbar" class="progress__fill"></div></div>
|
||||||
|
</main>
|
||||||
<.include!("./footer.html"); .>
|
<.include!("./footer.html"); .>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
import { Work, ServiceWorkerWork } from "./types";
|
import {Work, ServiceWorkerMessage} from "./types";
|
||||||
import fetchPoWConfig from "./fetchPoWConfig";
|
import fetchPoWConfig from "./fetchPoWConfig";
|
||||||
import sendWork from "./sendWork";
|
import sendWork from "./sendWork";
|
||||||
import sendToParent from "./sendToParent";
|
import sendToParent from "./sendToParent";
|
||||||
@@ -12,7 +12,17 @@ import * as CONST from "./const";
|
|||||||
import "./main.scss";
|
import "./main.scss";
|
||||||
|
|
||||||
let LOCK = false;
|
let LOCK = false;
|
||||||
const worker = new Worker("/bench.js");
|
|
||||||
|
const workerPromise = new Promise<Worker>((res) => {
|
||||||
|
const worker = new Worker("/bench.js");
|
||||||
|
worker.onmessage = (event: MessageEvent) => {
|
||||||
|
const message: ServiceWorkerMessage = event.data;
|
||||||
|
if(message.type === "ready") {
|
||||||
|
console.log("worker ready");
|
||||||
|
res(worker);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
/** add mcaptcha widget element to DOM */
|
/** add mcaptcha widget element to DOM */
|
||||||
export const registerVerificationEventHandler = (): void => {
|
export const registerVerificationEventHandler = (): void => {
|
||||||
@@ -20,10 +30,23 @@ export const registerVerificationEventHandler = (): void => {
|
|||||||
document.querySelector(".widget__verification-container")
|
document.querySelector(".widget__verification-container")
|
||||||
);
|
);
|
||||||
verificationContainer.style.display = "flex";
|
verificationContainer.style.display = "flex";
|
||||||
CONST.btn().addEventListener("click", (e) => solveCaptchaRunner(e));
|
workerPromise.then((worker: Worker) => {
|
||||||
|
const btn = CONST.btn();
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.addEventListener("click", (e) => solveCaptchaRunner(worker, e));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
export const solveCaptchaRunner = async (worker: Worker, e: Event): Promise<void> => {
|
||||||
|
const PROGRESS_FILL = <HTMLElement>document.querySelector(".progress__fill");
|
||||||
|
|
||||||
|
const setWidth = (width: number) => {
|
||||||
|
PROGRESS_FILL.style.width = `${width}%`;
|
||||||
|
PROGRESS_FILL.ariaValueNow = <any>parseInt(<any>width);
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = 0;
|
||||||
|
|
||||||
if (LOCK) {
|
if (LOCK) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
@@ -32,7 +55,10 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
|||||||
try {
|
try {
|
||||||
LOCK = true;
|
LOCK = true;
|
||||||
if (CONST.btn().checked == false) {
|
if (CONST.btn().checked == false) {
|
||||||
|
width = 0;
|
||||||
|
setWidth(width);
|
||||||
CONST.messageText().before();
|
CONST.messageText().before();
|
||||||
|
CONST.btn().ariaChecked = <any>false;
|
||||||
LOCK = false;
|
LOCK = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -43,32 +69,50 @@ export const solveCaptchaRunner = async (e: Event): Promise<void> => {
|
|||||||
CONST.messageText().during();
|
CONST.messageText().during();
|
||||||
// 1. get config
|
// 1. get config
|
||||||
const config = await fetchPoWConfig();
|
const config = await fetchPoWConfig();
|
||||||
|
const max_recorded_nonce = config.max_recorded_nonce;
|
||||||
// 2. prove work
|
// 2. prove work
|
||||||
worker.postMessage(config);
|
worker.postMessage(config);
|
||||||
|
|
||||||
worker.onmessage = async (event: MessageEvent) => {
|
worker.onmessage = async (event: MessageEvent) => {
|
||||||
const resp: ServiceWorkerWork = event.data;
|
const resp: ServiceWorkerMessage = event.data;
|
||||||
console.log(
|
|
||||||
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.work.time}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const proof: Work = {
|
if (resp.type === "work") {
|
||||||
key: CONST.sitekey(),
|
width = 80;
|
||||||
string: config.string,
|
setWidth(width);
|
||||||
nonce: resp.work.nonce,
|
console.log(
|
||||||
result: resp.work.result,
|
`Proof generated. Difficuly: ${config.difficulty_factor} Duration: ${resp.value.work.time}`
|
||||||
time: Math.trunc(resp.work.time),
|
);
|
||||||
worker_type: resp.work.worker_type,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3. submit work
|
const proof: Work = {
|
||||||
const token = await sendWork(proof);
|
key: CONST.sitekey(),
|
||||||
// 4. send token
|
string: config.string,
|
||||||
sendToParent(token);
|
nonce: resp.value.work.nonce,
|
||||||
// 5. mark checkbox checked
|
result: resp.value.work.result,
|
||||||
CONST.btn().checked = true;
|
time: Math.trunc(resp.value.work.time),
|
||||||
CONST.messageText().after();
|
worker_type: resp.value.work.worker_type,
|
||||||
LOCK = false;
|
};
|
||||||
|
|
||||||
|
width = 90;
|
||||||
|
setWidth(width);
|
||||||
|
// 3. submit work
|
||||||
|
const token = await sendWork(proof);
|
||||||
|
// 4. send token
|
||||||
|
sendToParent(token);
|
||||||
|
// 5. mark checkbox checked
|
||||||
|
CONST.btn().checked = true;
|
||||||
|
CONST.btn().ariaChecked = <any>true;
|
||||||
|
width = 100;
|
||||||
|
setWidth(width);
|
||||||
|
CONST.messageText().after();
|
||||||
|
LOCK = false;
|
||||||
|
}
|
||||||
|
if (resp.type === "progress") {
|
||||||
|
if (width < 80) {
|
||||||
|
width = resp.nonce / max_recorded_nonce * 100;
|
||||||
|
setWidth(width);
|
||||||
|
}
|
||||||
|
console.log(`received nonce ${resp.nonce}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
CONST.messageText().error();
|
CONST.messageText().error();
|
||||||
|
|||||||
@@ -7,106 +7,140 @@
|
|||||||
|
|
||||||
@import "../reset";
|
@import "../reset";
|
||||||
|
|
||||||
.widget__contaienr {
|
body {
|
||||||
align-items: center;
|
display: flex;
|
||||||
box-sizing: border-box;
|
flex-direction: column;
|
||||||
display: flex;
|
width: 100%;
|
||||||
height: 100%;
|
}
|
||||||
|
|
||||||
|
.widget__container {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
margin: auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget__inner-container {
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
border: 2px solid #e5e5e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__noscript-container {
|
.widget__noscript-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__noscript-warning {
|
.widget__noscript-warning {
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
flex: 2;
|
flex: 2;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__source-code {
|
.widget__source-code {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__verification-container {
|
.widget__verification-container {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: none;
|
display: flex;
|
||||||
line-height: 30px;
|
flex-direction: row-reverse;
|
||||||
font-size: 1rem;
|
line-height: 30px;
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__verification-checkbox {
|
.widget__verification-checkbox {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
|
||||||
|
|
||||||
#widget__verification-text--during {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#widget__verification-text--after {
|
|
||||||
display: none;
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
#widget__verification-text--error {
|
|
||||||
display: none;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget__verification-checkbox:checked ~ #widget__verification-text--before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget__verification-checkbox:checked ~ #widget__verification-text--during {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget__verification-checkbox:checked ~ #widget__verification-text--error {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget__verification-checkbox:checked ~ #widget__verification-text--after {
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__mcaptcha-details {
|
.widget__mcaptcha-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__mcaptcha-brand-name {
|
.widget__mcaptcha-brand-name {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__mcaptcha-logo {
|
.widget__mcaptcha-logo {
|
||||||
display: block;
|
display: block;
|
||||||
width: 35px;
|
width: 35px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__mcaptcha-info-container {
|
.widget__mcaptcha-info-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget__mcaptcha-info-link {
|
.widget__mcaptcha-info-link {
|
||||||
font-size: 0.5rem;
|
font-size: 0.5rem;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.widget__container {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget__inner-container {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
border: 2px solid #656569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget__verification-container {
|
||||||
|
color: rgb(232, 230, 227);
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget__mcaptcha-brand-name {
|
||||||
|
color: #7d94f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget__mcaptcha-info-link {
|
||||||
|
color: #7d94f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* progress bar courtesy of https://codepen.io/Bizzy-Coding/pen/poOymVJ?editors=1111 */
|
||||||
|
.progress__bar {
|
||||||
|
position: relative;
|
||||||
|
height: 5px;
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress__fill {
|
||||||
|
background: #65a2e0;
|
||||||
|
border-radius: 15px;
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.progress__bar {
|
||||||
|
background: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
import { gen_pow } from "@mcaptcha/pow-wasm";
|
import { stepped_gen_pow } from "@mcaptcha/pow-wasm";
|
||||||
import * as p from "@mcaptcha/pow_sha256-polyfill";
|
import * as p from "@mcaptcha/pow_sha256-polyfill";
|
||||||
import { WasmWork, PoWConfig, SubmitWork } from "./types";
|
import { WasmWork, PoWConfig, SubmitWork } from "./types";
|
||||||
|
|
||||||
@@ -12,19 +12,25 @@ import { WasmWork, PoWConfig, SubmitWork } from "./types";
|
|||||||
* @param {PoWConfig} config - the proof-of-work configuration using which
|
* @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<SubmitWork> => {
|
const prove = async (
|
||||||
|
config: PoWConfig,
|
||||||
|
progress: (nonce: number) => void
|
||||||
|
): Promise<SubmitWork> => {
|
||||||
const WASM = "wasm";
|
const WASM = "wasm";
|
||||||
const JS = "js";
|
const JS = "js";
|
||||||
|
const STEPS = 5000;
|
||||||
if (WasmSupported) {
|
if (WasmSupported) {
|
||||||
let proof: WasmWork = null;
|
let proof: WasmWork = null;
|
||||||
let res: SubmitWork = null;
|
let res: SubmitWork = null;
|
||||||
let time: number = null;
|
let time: number = null;
|
||||||
|
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
const proofString = gen_pow(
|
const proofString = stepped_gen_pow(
|
||||||
config.salt,
|
config.salt,
|
||||||
config.string,
|
config.string,
|
||||||
config.difficulty_factor
|
config.difficulty_factor,
|
||||||
|
STEPS,
|
||||||
|
(nonce: BigInt | number) => progress(Number(nonce))
|
||||||
);
|
);
|
||||||
const t1 = performance.now();
|
const t1 = performance.now();
|
||||||
time = t1 - t0;
|
time = t1 - t0;
|
||||||
@@ -47,10 +53,12 @@ const prove = async (config: PoWConfig): Promise<SubmitWork> => {
|
|||||||
|
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
|
|
||||||
proof = await p.generate_work(
|
proof = await p.stepped_generate_work(
|
||||||
config.salt,
|
config.salt,
|
||||||
config.string,
|
config.string,
|
||||||
config.difficulty_factor
|
config.difficulty_factor,
|
||||||
|
STEPS,
|
||||||
|
progress
|
||||||
);
|
);
|
||||||
const t1 = performance.now();
|
const t1 = performance.now();
|
||||||
time = t1 - t0;
|
time = t1 - t0;
|
||||||
|
|||||||
@@ -6,17 +6,37 @@
|
|||||||
import log from "../logger";
|
import log from "../logger";
|
||||||
|
|
||||||
import prove from "./prove";
|
import prove from "./prove";
|
||||||
import { PoWConfig, ServiceWorkerWork } from "./types";
|
import { PoWConfig, ServiceWorkerMessage, ServiceWorkerWork } from "./types";
|
||||||
|
|
||||||
log.log("worker registered");
|
log.log("worker registered");
|
||||||
|
|
||||||
|
const ready: ServiceWorkerMessage = {
|
||||||
|
type: "ready",
|
||||||
|
};
|
||||||
|
postMessage(ready);
|
||||||
|
|
||||||
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 work = await prove(config);
|
const progressCallback = (nonce: number) => {
|
||||||
const res: ServiceWorkerWork = {
|
const res: ServiceWorkerMessage = {
|
||||||
|
type: "progress",
|
||||||
|
nonce: nonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
postMessage(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
const work = await prove(config, progressCallback);
|
||||||
|
const w: ServiceWorkerWork = {
|
||||||
work,
|
work,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const res: ServiceWorkerMessage = {
|
||||||
|
type: "work",
|
||||||
|
value: w,
|
||||||
|
};
|
||||||
|
|
||||||
postMessage(res);
|
postMessage(res);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import * as CONST from "../const";
|
import * as CONST from "../const";
|
||||||
|
|
||||||
import {getBaseHtml, sitekey, checkbox} from "./setupTests";
|
import { getBaseHtml, sitekey, checkbox } from "./setupTests";
|
||||||
import * as TESTElements from "./setupTests";
|
import * as TESTElements from "./setupTests";
|
||||||
|
|
||||||
it("const works", () => {
|
it("const works", () => {
|
||||||
@@ -17,29 +17,17 @@ it("const works", () => {
|
|||||||
|
|
||||||
// display after
|
// display after
|
||||||
CONST.messageText().after();
|
CONST.messageText().after();
|
||||||
expect(TESTElements.afterMsg.style.display).toBe("block");
|
expect(TESTElements.Msg.innerText).toBe(CONST.AFTER);
|
||||||
expect(TESTElements.beforeMsg.style.display).toBe("none");
|
|
||||||
expect(TESTElements.duringMsg.style.display).toBe("none");
|
|
||||||
expect(TESTElements.errorMsg.style.display).toBe("none");
|
|
||||||
|
|
||||||
// display before
|
// display before
|
||||||
CONST.messageText().before();
|
CONST.messageText().before();
|
||||||
expect(TESTElements.afterMsg.style.display).toBe("none");
|
expect(TESTElements.Msg.innerText).toBe(CONST.BEFORE);
|
||||||
expect(TESTElements.beforeMsg.style.display).toBe("block");
|
|
||||||
expect(TESTElements.duringMsg.style.display).toBe("none");
|
|
||||||
expect(TESTElements.errorMsg.style.display).toBe("none");
|
|
||||||
|
|
||||||
// display during
|
// display during
|
||||||
CONST.messageText().during();
|
CONST.messageText().during();
|
||||||
expect(TESTElements.afterMsg.style.display).toBe("none");
|
expect(TESTElements.Msg.innerText).toBe(CONST.DURING);
|
||||||
expect(TESTElements.beforeMsg.style.display).toBe("none");
|
|
||||||
expect(TESTElements.duringMsg.style.display).toBe("block");
|
|
||||||
expect(TESTElements.errorMsg.style.display).toBe("none");
|
|
||||||
|
|
||||||
// display error
|
// display error
|
||||||
CONST.messageText().error();
|
CONST.messageText().error();
|
||||||
expect(TESTElements.afterMsg.style.display).toBe("none");
|
expect(TESTElements.Msg.innerText).toBe(CONST.ERROR);
|
||||||
expect(TESTElements.beforeMsg.style.display).toBe("none");
|
|
||||||
expect(TESTElements.duringMsg.style.display).toBe("none");
|
|
||||||
expect(TESTElements.errorMsg.style.display).toBe("block");
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,25 +11,19 @@ export const checkbox = <HTMLInputElement>document.createElement("input");
|
|||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
checkbox.id = CONST.btnId;
|
checkbox.id = CONST.btnId;
|
||||||
|
|
||||||
const getMessages = (state: string) => {
|
const getMessages = () => {
|
||||||
const msg = <HTMLElement>document.createElement("span");
|
const msg = <HTMLElement>document.createElement("span");
|
||||||
msg.id = `widget__verification-text--${state}`;
|
msg.id = "widget__verification-text";
|
||||||
|
msg.innerText = "I'm not a robot";
|
||||||
return msg;
|
return msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const beforeMsg = getMessages("before");
|
export const Msg = getMessages();
|
||||||
export const afterMsg = getMessages("after");
|
|
||||||
export const duringMsg = getMessages("during");
|
|
||||||
export const errorMsg = getMessages("error");
|
|
||||||
|
|
||||||
/** get base HTML with empty mCaptcha container */
|
/** get base HTML with empty mCaptcha container */
|
||||||
export const getBaseHtml = (): HTMLFormElement => {
|
export const getBaseHtml = (): HTMLFormElement => {
|
||||||
const form = <HTMLFormElement>document.createElement("form");
|
const form = <HTMLFormElement>document.createElement("form");
|
||||||
form.appendChild(checkbox);
|
form.appendChild(checkbox);
|
||||||
form.appendChild(beforeMsg);
|
form.appendChild(Msg);
|
||||||
form.appendChild(duringMsg);
|
|
||||||
form.appendChild(afterMsg);
|
|
||||||
form.appendChild(errorMsg);
|
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,8 +32,14 @@ export type PoWConfig = {
|
|||||||
string: string;
|
string: string;
|
||||||
difficulty_factor: number;
|
difficulty_factor: number;
|
||||||
salt: string;
|
salt: string;
|
||||||
|
max_recorded_nonce: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Token = {
|
export type Token = {
|
||||||
token: string;
|
token: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ServiceWorkerMessage =
|
||||||
|
| { type: "ready" }
|
||||||
|
| { type: "work"; value: ServiceWorkerWork }
|
||||||
|
| { type: "progress"; nonce: number };
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@@ -631,15 +631,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@mcaptcha/core-glue/-/core-glue-0.1.0-rc1.tgz#76d665a3fc537062061e12e274f969ac3e053685"
|
resolved "https://registry.yarnpkg.com/@mcaptcha/core-glue/-/core-glue-0.1.0-rc1.tgz#76d665a3fc537062061e12e274f969ac3e053685"
|
||||||
integrity sha512-P4SgUioJDR38QpnP9sPY72NyaYex8MXD6RbzrfKra+ngamT26XjqVZEHBiZU2RT7u0SsWhuko4N1ntNOghsgpg==
|
integrity sha512-P4SgUioJDR38QpnP9sPY72NyaYex8MXD6RbzrfKra+ngamT26XjqVZEHBiZU2RT7u0SsWhuko4N1ntNOghsgpg==
|
||||||
|
|
||||||
"@mcaptcha/pow-wasm@^0.1.0-rc1":
|
"@mcaptcha/pow-wasm@^0.1.0-rc2":
|
||||||
version "0.1.0-rc1"
|
version "0.1.0-rc2"
|
||||||
resolved "https://registry.yarnpkg.com/@mcaptcha/pow-wasm/-/pow-wasm-0.1.0-rc1.tgz#eef8409e0c74e9c7261587bdebd80a8c4af92f9e"
|
resolved "https://registry.yarnpkg.com/@mcaptcha/pow-wasm/-/pow-wasm-0.1.0-rc2.tgz#c7aaa678325600a178b11a702e2aeb9f8143e605"
|
||||||
integrity sha512-7+PGKoe1StFRsa9TEqztzK4/obbdY4OfavFX+geTk8b3K26D+eHPyimJ9BPlpI1VZl8ujR3CnbfbnQSRaqS7ZQ==
|
integrity sha512-2G0nQ2GQWECRcE5kzfULDsQ032s6/PDzE1rncMdQAR1Mu2YQfFZHgnX4zLJmQnjKIhy9meIjXvatVSyIllrbtg==
|
||||||
|
|
||||||
"@mcaptcha/pow_sha256-polyfill@^0.1.0-rc1":
|
"@mcaptcha/pow_sha256-polyfill@^0.1.0-rc2":
|
||||||
version "0.1.0-rc1"
|
version "0.1.0-rc2"
|
||||||
resolved "https://registry.yarnpkg.com/@mcaptcha/pow_sha256-polyfill/-/pow_sha256-polyfill-0.1.0-rc1.tgz#dfeee88f5f6fd99aeae65dbcff6fbb09fe8a1696"
|
resolved "https://registry.yarnpkg.com/@mcaptcha/pow_sha256-polyfill/-/pow_sha256-polyfill-0.1.0-rc2.tgz#253320e7a6666e395ef9dfb123d1102066d72b87"
|
||||||
integrity sha512-OFA4W3/vh8ORUnifbm8c/8eP22CbiXr4Un6/l4fMyqLj1aoQLMGAiuqab0trGqBnY0DU2bwTMyxflx26/cWgIw==
|
integrity sha512-ERIbxIo+ZnQKtti/T4FLmcY0neuc5R05L97qYc62Hm++i+3dx/W6A8oC4V9U0XKCPYnHZFoZozAZlbsGXjrsVQ==
|
||||||
|
|
||||||
"@mcaptcha/vanilla-glue@^0.1.0-rc1":
|
"@mcaptcha/vanilla-glue@^0.1.0-rc1":
|
||||||
version "0.1.0-rc1"
|
version "0.1.0-rc1"
|
||||||
|
|||||||
Reference in New Issue
Block a user