8 Commits

Author SHA1 Message Date
c3bfd4f920 Merge pull request 'Версия 0.7.1.' (#2) from develop into main
Reviewed-on: #2
2023-12-09 00:59:41 +06:00
172a9460df Fix https on prod.
Added the force_https parameter to env APP_FORCE_HTTPS. This will force https to be enabled.
2023-12-09 00:55:56 +06:00
a5ba9eb3f8 I added (int) to the configuration in the parameters, where we get the numbers through the env function. 2023-12-09 00:54:12 +06:00
5ea5d4d0ba Merge pull request 'Version 0.7.0' (#1) from develop into main
Reviewed-on: #1
2023-12-08 21:18:22 +06:00
e72dc03589 Update README with project specifics.
The README file has been updated to provide specific details about the current project.
2023-12-08 21:15:51 +06:00
6bf2bc793b Add Docker setup for production environment. 2023-12-08 21:10:31 +06:00
8ccbd5000d Add demo mode restriction to CaptchaToken destroy method.
This commit adds a check to the `destroy` method in the `CaptchaTokenService`. It uses the `Helpers::isDemoModeAndUserDenyUpdate` function to prevent users from deleting tokens while the application is in demo mode. This was added to protect the application's state during presentations or demos.
2023-12-05 21:48:02 +06:00
a52b148101 Add license information for captcha backgrounds. 2023-12-05 21:01:49 +06:00
15 changed files with 428 additions and 51 deletions

View File

@@ -4,6 +4,8 @@ APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_FORCE_HTTPS=false
APP_DEMO_MODE=false
APP_DEMO_EMAIL=
APP_DEMO_PASSWORD=

View File

@@ -1,66 +1,50 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
## О проекте
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
Захотелось написать свой независимый сервис защиты от роботов. Сервис каптча написан на фреймворке Laravel. Вдохновлялся, а так же брал картинки с проекта <a href="https://github.com/wenlng/go-captcha" target="_blank">Go Captcha</a>.
## About Laravel
## Зависимости
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
php 8.2 (модули: redis, gd)
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
redis
Laravel is accessible, powerful, and provides tools required for large, robust applications.
mysql 8
## Learning Laravel
## Демострация
Демо сервис каптча: https://captcha-admin-demo.tut-site.net/
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
Email: demo@tut-site.net
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
Пароль: demodemo
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
Демо каптча: https://captcha-demo.tut-site.net/
## Laravel Sponsors
## API
https://captcha-admin-demo.tut-site.net/api-docs/
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
## Javascript клиент для сайта
https://git.kor-elf.net/kor-elf/captcha-rule-for-laravel
### Premium Partners
## Как проверять со стороны бэкенда
Для Laravel 10 есть готовый пакет: https://git.kor-elf.net/kor-elf/captcha-rule-for-laravel
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Cubet Techno Labs](https://cubettech.com)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[Many](https://www.many.co.uk)**
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
- **[DevSquad](https://devsquad.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[OP.GG](https://op.gg)**
- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
- **[Lendio](https://lendio.com)**
Можно установить этот пакет так: composer require kor-elf/captcha-rule-for-laravel
## Contributing
<br><b>Curl:</b>
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
curl -X POST 'https://captcha-admin-demo.tut-site.net/api/v1/captcha/{captcha-token}' -H 'private-token: {your-private-token}' -H 'Content-Type: application/json' -d '{"user_agent": "{user-agent}"}' --max-time 10
## Code of Conduct
Где {captcha-token} - токен получил пользователь от сервиса каптча после успешной проверки.
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
Где {your-private-token} - приватный токен, который мы создали в админке.
## Security Vulnerabilities
Где {user-agent} - передаём user agent от пользователя, который проходил каптчу.
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
Успешная проверка пользователя вернёт ответ код 200 и status = true. Иначе считаем, что пользователь не прошёл проверку на робота.
## License
## Репозиторий с демо
https://git.kor-elf.net/kor-elf/service-captcha-demo
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
## Лицензия
[MIT license](https://opensource.org/licenses/MIT).

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -36,4 +37,9 @@ final class CaptchaToken extends Model
{
return $this->hasMany(Captcha::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -22,6 +22,7 @@ use App\Services\Search\Search;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
@@ -70,6 +71,10 @@ final class AppServiceProvider extends ServiceProvider
return Helpers::isDemoMode();
});
if (config('app.force_https') === true) {
URL::forceScheme('https');
}
Password::defaults(function () {
$rule = Password::min(8);

View File

@@ -5,6 +5,7 @@ namespace App\Services\Private;
use App\Dto\Builder\CaptchaToken as CaptchaTokenDto;
use App\Dto\QuerySettingsDto;
use App\Dto\Request\Private\CaptchaToken\StoreUpdate;
use App\Helpers\Helpers;
use App\Models\User;
use App\Models\CaptchaToken;
use App\Repositories\CaptchaTokenRepository;
@@ -133,6 +134,10 @@ final class CaptchaTokenService extends Service
return $this->errFobidden(__('Access is denied'));
}
if (Helpers::isDemoModeAndUserDenyUpdate($modelCaptchaToken->user)) {
return $this->errValidate(__('Demo Mode'));
}
try {
DB::transaction(function () use ($modelCaptchaToken) {
$this->captchaTokenHandler->handleDestroy($modelCaptchaToken);

View File

@@ -70,6 +70,8 @@ return [
'asset_url' => env('ASSET_URL'),
'force_https' => (bool) env('APP_FORCE_HTTPS', false),
/*
|--------------------------------------------------------------------------
| Application Timezone

View File

@@ -1,10 +1,10 @@
<?php
return [
'waiting_for_captcha_verification_in_seconds' => env('WAITING_FOR_CAPTCHA_VERIFICATION_IN_SECONDS', 900),
'validate_max_count_errors' => env('CAPTCHA_VALIDATE_MAX_COUNT_ERRORS', 3),
'max_info_display_count' => env('CAPTCHA_MAX_INFO_DISPLAY_COUNT', 1),
'verification_data_view_limit_in_minutes' => env('CAPTCHA_VERIFICATION_DATA_VIEW_LIMIT_IN_MINUTES', 60),
'waiting_for_captcha_verification_in_seconds' => (int) env('WAITING_FOR_CAPTCHA_VERIFICATION_IN_SECONDS', 900),
'validate_max_count_errors' => (int) env('CAPTCHA_VALIDATE_MAX_COUNT_ERRORS', 3),
'max_info_display_count' => (int) env('CAPTCHA_MAX_INFO_DISPLAY_COUNT', 1),
'verification_data_view_limit_in_minutes' => (int) env('CAPTCHA_VERIFICATION_DATA_VIEW_LIMIT_IN_MINUTES', 60),
'imageClass' => \App\Captcha\Images\Image::class,
'types' => [
'string' => [

View File

@@ -3,6 +3,6 @@ return [
/**
* Max limit of the hour.
*/
'login_max_request' => env('LOGIN_MAX_REQUEST', 50),
'login_max_email_request' => env('LOGIN_MAX_EMAIL_REQUEST', 10),
'login_max_request' => (int) env('LOGIN_MAX_REQUEST', 50),
'login_max_email_request' => (int) env('LOGIN_MAX_EMAIL_REQUEST', 10),
];

42
docker-compose-prod.yml Normal file
View File

@@ -0,0 +1,42 @@
version: '3.7'
services:
nginx:
build:
context: ./docker/nginx
dockerfile: Dockerfile
depends_on:
- app
- swagger
ports:
- ${DOCKER_CAPTCHA_NGINX_PORT}:80
app:
depends_on:
- redis
build:
context: ./docker/app
dockerfile: Dockerfile
target: PRODUCTION
# restart: always
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
- CHOWN
- FOWNER
ports:
- "9000"
env_file: .env
volumes:
- /etc/localtime:/etc/localtime:ro
swagger:
image: swaggerapi/swagger-ui
depends_on:
- app
environment:
URLS: "[ { url: '/swagger.json', name: '/swagger.json' } ]"
BASE_URL: /api-docs
ports:
- "8080"
redis:
image: redis:3.0-alpine

87
docker/app/Dockerfile Normal file
View File

@@ -0,0 +1,87 @@
FROM docker.io/php:8.2-zts-alpine3.18 AS UNIT_BUILDER
ARG UNIT_VERSION=1.31.1
RUN apk --no-cache add pcre2-dev gcc git musl-dev make && \
mkdir -p /usr/lib/unit/modules && \
git clone https://github.com/nginx/unit.git && \
cd unit && \
git checkout $UNIT_VERSION && \
./configure --prefix=/var --statedir=/var/lib/unit --runstatedir=/var/run --control=unix:/run/unit/control.unit.sock --log=/var/log/unit.log --user=www-data --group=www-data --tmpdir=/tmp --modulesdir=/var/lib/unit/modules && \
./configure php && \
make && \
make install
FROM docker.io/php:8.2-zts-alpine3.18 as BUILD
COPY --from=UNIT_BUILDER /var/sbin/unitd /usr/sbin/unitd
COPY --from=UNIT_BUILDER /var/lib/unit/ /var/lib/unit/
COPY docker-entrypoint.sh /home/unit/docker-entrypoint.sh
COPY unit-config.json /docker-entrypoint.d/config.json
RUN apk --no-cache add pcre2 libbz2 libpng libwebp libjpeg-turbo icu-libs freetype oniguruma libzip \
&& apk add --no-cache --virtual .phpize-deps icu-dev libpng-dev bzip2-dev libwebp-dev libjpeg-turbo-dev freetype-dev oniguruma-dev libzip-dev pcre2-dev ${PHPIZE_DEPS} \
&& docker-php-ext-configure intl --enable-intl && \
docker-php-ext-configure bcmath --enable-bcmath && \
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp && \
docker-php-ext-install -j$(nproc) gd && \
docker-php-ext-install bcmath &&\
docker-php-ext-install pdo \
mysqli pdo_mysql \
intl mbstring \
zip pcntl \
exif opcache bz2 \
calendar \
&& pear update-channels && pecl update-channels \
&& pecl install redis && docker-php-ext-enable redis \
&& rm -rf /tmp/pear \
&& docker-php-source delete \
&& apk del .phpize-deps \
&& rm -rf /var/cache/apk/* && rm -rf /etc/apk/cache \
&& rm -rf /usr/share/php && rm -rf /tmp/* \
&& rm "$PHP_INI_DIR/php.ini-development" \
&& mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& mkdir -p /tmp/php/upload \
&& mkdir -p /tmp/php/sys \
&& mkdir -p /tmp/php/session \
&& chown -R www-data:www-data /tmp/php \
&& ln -sf /dev/stdout /var/log/unit.log \
&& addgroup -S unit && adduser -S unit -G unit \
&& chmod 755 /home/unit/docker-entrypoint.sh
FROM BUILD as APP_BUILD_FOR_PRODUCTION
WORKDIR /home/app
RUN apk --no-cache add git nodejs npm \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& git clone https://git.kor-elf.net/kor-elf/service-captcha.git . \
&& composer install --optimize-autoloader --no-dev \
&& npm install && npm run build \
&& rm -rf /home/app/node_modules /home/app/.git /home/app/docker
#
FROM BUILD AS PRODUCTION
COPY --from=APP_BUILD_FOR_PRODUCTION /home/app /var/www/html
WORKDIR /var/www/html
STOPSIGNAL SIGTERM
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
EXPOSE 9000
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]
#
FROM BUILD AS DEVELOP
WORKDIR /var/www/html
STOPSIGNAL SIGTERM
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENTRYPOINT ["/home/unit/docker-entrypoint.sh"]
EXPOSE 9000
CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock", "--user", "unit", "--group", "unit"]

View File

@@ -0,0 +1,111 @@
#!/bin/sh
set -euo pipefail
WAITLOOPS=5
SLEEPSEC=1
unitd="unitd"
curl_put()
{
RET=$(/usr/bin/curl -s -w '%{http_code}' -X PUT --data-binary @$1 --unix-socket /var/run/control.unit.sock http://localhost/$2)
RET_BODY=$(echo $RET | /bin/sed '$ s/...$//')
RET_STATUS=$(echo $RET | /usr/bin/tail -c 4)
if [ "$RET_STATUS" -ne "200" ]; then
echo "$0: Error: HTTP response status code is '$RET_STATUS'"
echo "$RET_BODY"
return 1
else
echo "$0: OK: HTTP response status code is '$RET_STATUS'"
echo "$RET_BODY"
fi
return 0
}
if [ "$unitd" = "unitd" ] || [ "$unitd" = "unitd-debug" ]; then
echo "$0: Launching Unit daemon to perform initial configuration..."
/usr/sbin/$unitd --control unix:/var/run/control.unit.sock
for i in $(/usr/bin/seq $WAITLOOPS); do
if [ ! -S /var/run/control.unit.sock ]; then
echo "$0: Waiting for control socket to be created..."
/bin/sleep $SLEEPSEC
else
break
fi
done
# even when the control socket exists, it does not mean unit has finished initialisation
# this curl call will get a reply once unit is fully launched
/usr/bin/curl -s -X GET --unix-socket /var/run/control.unit.sock http://localhost/
if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then
echo "$0: /docker-entrypoint.d/ is not empty, applying initial configuration..."
echo "$0: Looking for certificate bundles in /docker-entrypoint.d/..."
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.pem"); do
echo "$0: Uploading certificates bundle: $f"
curl_put $f "certificates/$(basename $f .pem)"
done
echo "$0: Looking for JavaScript modules in /docker-entrypoint.d/..."
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.js"); do
echo "$0: Uploading JavaScript module: $f"
curl_put $f "js_modules/$(basename $f .js)"
done
echo "$0: Looking for configuration snippets in /docker-entrypoint.d/..."
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.json"); do
echo "$0: Applying configuration $f";
curl_put $f "config"
done
if [ ! -z ${UNIT_SOURCE+x} ]
then
echo "[${UNIT_SOURCE}]" > /docker-entrypoint.d/unit_source.json
curl_put "/docker-entrypoint.d/unit_source.json" "config/listeners/*:9000/forwarded/source"
fi
echo "$0: Looking for shell scripts in /docker-entrypoint.d/..."
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.sh"); do
echo "$0: Launching $f";
"$f"
done
# warn on filetypes we don't know what to do with
for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -not -name "*.sh" -not -name "*.json" -not -name "*.pem" -not -name "*.js"); do
echo "$0: Ignoring $f";
done
fi
echo "$0: Stopping Unit daemon after initial configuration..."
kill -TERM $(/bin/cat /var/run/unit.pid)
for i in $(/usr/bin/seq $WAITLOOPS); do
if [ -S /var/run/control.unit.sock ]; then
echo "$0: Waiting for control socket to be removed..."
/bin/sleep $SLEEPSEC
else
break
fi
done
if [ -S /var/run/control.unit.sock ]; then
kill -KILL $(/bin/cat /var/run/unit.pid)
rm -f /var/run/control.unit.sock
fi
echo
echo "$0: Unit initial configuration complete; ready for start up..."
echo
fi
php artisan config:cache
php artisan event:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
chown -R unit:unit /var/www/html
chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
chmod -R 777 /var/www/html/storage /var/www/html/bootstrap/cache
exec "$@"

View File

@@ -0,0 +1,69 @@
{
"listeners": {
"*:9000": {
"pass": "routes",
"forwarded": {
"client_ip": "X-Forwarded-For",
"recursive": false,
"source": [
]
}
}
},
"routes": [
{
"match": {
"uri": [
"/index.php/",
"~^/index\\.php/.*",
"~\\.php$"
]
},
"action": {
"return": 404
}
},
{
"action": {
"share": "/var/www/html/public$uri",
"fallback": {
"pass": "applications/laravel"
}
}
}
],
"applications": {
"laravel": {
"type": "php",
"root": "/var/www/html/public",
"working_directory": "/var/www/html",
"user": "www-data",
"group": "www-data",
"script": "index.php",
"processes": {
"max": 10,
"spare": 5,
"idle_timeout": 20
},
"options": {
"file": "/usr/local/etc/php/php.ini",
"admin": {
"upload_tmp_dir": "/tmp/php/upload",
"sys_temp_dir": "/tmp/php/sys",
"session.save_path": "/tmp/php/session",
"open_basedir": "/var/www/html:/tmp/php:.",
"memory_limit": "256M",
"upload_max_filesize": "20M",
"post_max_size": "20M",
"expose_php": "0"
},
"user": {
"display_errors": "0"
}
}
}
}
}

3
docker/nginx/Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM nginx:alpine3.18-slim
COPY nginx.conf /etc/nginx/conf.d/default.conf

36
docker/nginx/nginx.conf Normal file
View File

@@ -0,0 +1,36 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name captcha;
client_max_body_size 1024M;
root /var/www/html/public;
location / {
location /api-docs {
proxy_pass http://swagger:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
}
location / {
proxy_pass http://app:9000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
}
}
}

View File

@@ -0,0 +1,25 @@
1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg
https://github.com/wenlng/go-captcha/blob/master/LICENSE
MIT License
Copyright (c) 2021 go-captcha
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.