mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
6e47d76ba6
Client file sharing, rebuilt from the ground up: a private area per client, resumable uploads, folders, groups and categories, sharing with expiry dates and download limits, comments, file versions, an activity log, a REST API, and sixteen languages. This repository begins here. ProjectSend 2 was developed privately, and that development history is not published — the previous generation remains available, with its own history, at projectsend/legacy. Free software under the GNU General Public License v2, or (at your option) any later version.
90 lines
3.2 KiB
PHP
90 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Platform\Captcha;
|
|
|
|
/**
|
|
* The CAPTCHA services this installation can be configured against.
|
|
*
|
|
* All three are v1 parity, so an install coming across keeps whichever it
|
|
* had rather than being told to re-key. They differ in ways the rest of
|
|
* the module has to respect, and the three predicates below are the whole
|
|
* of that difference:
|
|
*
|
|
* - Turnstile and reCAPTCHA v2 render something the visitor interacts
|
|
* with; v3 is invisible and mints a token at submit time instead.
|
|
* - v3 is the only one that scores rather than decides, so it is the only
|
|
* one with a threshold.
|
|
* - v3 and Turnstile echo back the `action` the token was minted for; v2
|
|
* does not, and so cannot be bound to a form.
|
|
*/
|
|
enum CaptchaProvider: string
|
|
{
|
|
case Turnstile = 'turnstile';
|
|
case RecaptchaV2 = 'recaptcha_v2';
|
|
case RecaptchaV3 = 'recaptcha_v3';
|
|
|
|
/**
|
|
* v1 hardcoded these in English inside each provider's constructor,
|
|
* which is why "Cloudflare Turnstile" was untranslatable there.
|
|
*/
|
|
public function label(): string
|
|
{
|
|
return match ($this) {
|
|
self::Turnstile => __('Cloudflare Turnstile'),
|
|
self::RecaptchaV2 => __('reCAPTCHA v2'),
|
|
self::RecaptchaV3 => __('reCAPTCHA v3'),
|
|
};
|
|
}
|
|
|
|
public function verifyUrl(): string
|
|
{
|
|
return match ($this) {
|
|
self::Turnstile => 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
|
|
self::RecaptchaV2, self::RecaptchaV3 => 'https://www.google.com/recaptcha/api/siteverify',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The vendor script this provider's widget needs.
|
|
*
|
|
* `render=explicit` for the two that draw a widget: auto-rendering
|
|
* scans the document for a magic class, which is what made two widgets
|
|
* on one page break in v1. v3 draws nothing and instead wants the site
|
|
* key baked into the URL so `grecaptcha.execute` can be called later.
|
|
*/
|
|
public function scriptUrl(string $siteKey): string
|
|
{
|
|
return match ($this) {
|
|
self::Turnstile => 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit',
|
|
self::RecaptchaV2 => 'https://www.google.com/recaptcha/api.js?render=explicit',
|
|
self::RecaptchaV3 => 'https://www.google.com/recaptcha/api.js?render='.urlencode($siteKey),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Whether a siteverify response carries a `score` that has to clear a
|
|
* threshold, rather than a plain yes or no.
|
|
*/
|
|
public function usesScore(): bool
|
|
{
|
|
return $this === self::RecaptchaV3;
|
|
}
|
|
|
|
/**
|
|
* Whether siteverify echoes back the action the token was minted for.
|
|
*
|
|
* When it does, a token from the login form can be refused at the
|
|
* registration form — the replay hole v1 left open by hardcoding the
|
|
* action to "submit" and never reading it back. reCAPTCHA v2 returns
|
|
* no action at all and cannot be bound this way; the residual risk is
|
|
* bounded by tokens being single-use, short-lived, site-key-scoped and
|
|
* behind the same throttles as everything else.
|
|
*/
|
|
public function bindsAction(): bool
|
|
{
|
|
return $this !== self::RecaptchaV2;
|
|
}
|
|
}
|