You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pixelfed/app/Services/Captcha/CapDriver.php

113 lines
3.3 KiB
PHTML

1 week ago
<?php
namespace App\Services\Captcha;
use App\Contracts\CaptchaDriver;
use Illuminate\Http\Client\Factory as HttpFactory;
use LaravelCap\Cap;
/**
* Cap driver (self-hosted proof-of-work CAPTCHA).
*
* Wraps the oliweb/laravel-cap package for verification, and renders the
1 week ago
*
1 week ago
* @cap.js/widget from the jsDelivr CDN.
*
* The full API endpoint the widget and verifier talk to is composed from a base
* URL (captcha.cap.endpoint) plus the site key (captcha.cap.sitekey):
*
* https://cap.example.com + 3c87a0e810 => https://cap.example.com/3c87a0e810/
1 week ago
*
* @see https://github.com/oliweb-ch/laravel-cap
*/
class CapDriver implements CaptchaDriver
{
/**
* Default @cap.js/widget version served from the CDN. "latest" tracks the
* newest stable release; override via captcha.cap.widget_version.
*/
private const DEFAULT_WIDGET_VERSION = 'latest';
public function name(): string
{
return 'cap';
}
public function isConfigured(): bool
{
return ! empty(config_cache('captcha.cap.endpoint'))
1 week ago
&& ! empty(config_cache('captcha.cap.sitekey'))
1 week ago
&& ! empty(config_cache('captcha.cap.secret'));
}
public function responseField(): string
{
return (string) config('captcha.cap.token_field', 'cap-token');
}
1 week ago
/**
* Compose the full Cap API endpoint: "{base}/{sitekey}/".
*
* The base URL is the instance origin without the site key. The site key is
* appended as a path segment with a trailing slash (required by Cap).
*/
public function apiEndpoint(): string
{
$base = rtrim(trim((string) config_cache('captcha.cap.endpoint')), '/');
$sitekey = trim((string) config_cache('captcha.cap.sitekey'), '/ ');
if ($base === '' || $sitekey === '') {
return '';
}
return $base.'/'.$sitekey.'/';
}
1 week ago
public function verify(array $input): bool
{
$token = $input[$this->responseField()] ?? null;
if (empty($token)) {
return false;
}
1 week ago
$endpoint = $this->apiEndpoint();
if ($endpoint === '') {
return false;
}
1 week ago
$cap = new Cap(app(HttpFactory::class), [
1 week ago
'endpoint' => $endpoint,
1 week ago
'secret' => config_cache('captcha.cap.secret'),
'timeout' => (int) config('captcha.cap.timeout', 5),
'fail_open' => (bool) config('captcha.cap.fail_open', false),
]);
return $cap->verify((string) $token);
}
public function render(array $attributes = []): string
{
1 week ago
$endpoint = e($this->apiEndpoint());
1 week ago
$field = e($this->responseField());
$attrs = '';
foreach ($attributes as $key => $value) {
$attrs .= ' '.e($key).'="'.e($value).'"';
}
return '<cap-widget data-cap-api-endpoint="'.$endpoint.'"'
.' data-cap-hidden-field-name="'.$field.'"'.$attrs.'></cap-widget>';
}
public function scripts(): string
{
// Load the widget from the jsDelivr CDN. Defaults to the "latest"
// stable release; pin a specific version via captcha.cap.widget_version.
$version = trim((string) config('captcha.cap.widget_version')) ?: self::DEFAULT_WIDGET_VERSION;
$src = 'https://cdn.jsdelivr.net/npm/@cap.js/widget@'.$version;
return '<script src="'.e($src).'"></script>';
}
}