Added recaptcha

This commit is contained in:
2026-01-12 11:05:01 +01:00
parent a4d386f2c5
commit 01861f08c6
6 changed files with 101 additions and 45 deletions

View File

@@ -4,26 +4,47 @@
## Anpassung der Useranmeldung ## Anpassung der Useranmeldung
#### Registrierung ohne Captcha und E-Mail-Verifikation #### Registrierung mit Google reCAPTCHA v3
**Beschreibung:** **Beschreibung:**
Die Registrierung wurde so angepasst, dass Benutzer ohne Captcha und E-Mail-Verifikation registriert werden können. Benutzer werden nach der Registrierung automatisch aktiviert. Die Registrierung wurde mit Google reCAPTCHA v3 abgesichert. reCAPTCHA v3 arbeitet unsichtbar im Hintergrund und analysiert das Benutzerverhalten, um Bots zu erkennen. E-Mail-Verifizierung ist deaktiviert - Benutzer werden nach der Registrierung automatisch aktiviert.
**Technische Umsetzung:** **Technische Umsetzung:**
- Entfernung des Captcha-Features aus dem Registrierungsprozess - Integration von Google reCAPTCHA v3 (unsichtbar, keine Checkbox)
- Deaktivierung der E-Mail-Verifizierung - Score-basierte Validierung (0.0 = Bot, 1.0 = Mensch)
- Automatische Aktivierung neuer Benutzerkonten - Blockierung bei Score unter 0.5
- Vereinfachung des Registrierungsformulars - Konfiguration über `RECAPTCHA_SITE_KEY` und `RECAPTCHA_SECRET_KEY`
**Admin-Funktion:** **Code-Beispiel (View):**
- Nur Administratoren können über das gleiche Formular neue Benutzer anlegen ```html
- Sicherstellung, dass nur autorisierte Personen Benutzerkonten erstellen können <script src="https://www.google.com/recaptcha/api.js?render=SITE_KEY"></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('SITE_KEY', {action: 'register'}).then(function(token) {
document.getElementById('recaptcha-response').value = token;
});
});
</script>
```
**Code-Beispiel (Server-Validierung):**
```php
$response = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $recaptcha_response);
$response_data = json_decode($response);
if (!$response_data->success || $response_data->score < 0.5) {
// Bot erkannt oder Validierung fehlgeschlagen
}
```
**Vorteile von v3:**
- Keine Benutzerinteraktion erforderlich
- Bessere User Experience
- Intelligente Bot-Erkennung durch Verhaltensanalyse
``` ```
<!-- Screenshot Platzhalter --> <!-- Screenshot Platzhalter -->
[📸 Screenshot: Vereinfachtes Registrierungsformular] [📸 Screenshot: Registrierungsformular]
[📸 Screenshot: Admin-Benutzererstellung] [📸 Screenshot: reCAPTCHA Badge]
[📸 Screenshot: Automatisch aktivierter Benutzer]
``` ```
--- ---

View File

@@ -70,12 +70,10 @@ return array(
'DB_PORT' => '3306', 'DB_PORT' => '3306',
'DB_CHARSET' => 'utf8', 'DB_CHARSET' => 'utf8',
/** /**
* Configuration for: Captcha size * Configuration for: Google reCAPTCHA v2
* The currently used Captcha generator (https://github.com/Gregwar/Captcha) also runs without giving a size,
* so feel free to use ->build(); inside CaptchaModel.
*/ */
'CAPTCHA_WIDTH' => 359, 'RECAPTCHA_SITE_KEY' => 'recaptcha-site-key',
'CAPTCHA_HEIGHT' => 100, 'RECAPTCHA_SECRET_KEY' => 'recaptcha-secret-key',
/** /**
* Configuration for: Cookies * Configuration for: Cookies
* 1209600 seconds = 2 weeks * 1209600 seconds = 2 weeks

View File

@@ -22,23 +22,25 @@ class RegisterController extends Controller
*/ */
public function index() public function index()
{ {
// only admins can access registration; reuse existing admin auth check if (Session::userIsLoggedIn()) {
Auth::checkAdminAuthentication(); Redirect::to('index');
return;
}
$this->View->render('register/index'); $this->View->render('register/index');
} }
/**
* Register page action
* POST-request after form submit
*/
public function register_action() public function register_action()
{ {
// enforce admin-only for registration if (Session::userIsLoggedIn()) {
Auth::checkAdminAuthentication(); Redirect::to('index');
return;
}
RegistrationModel::registerNewUser(); if (RegistrationModel::registerNewUser()) {
Redirect::to('login');
Redirect::to('admin/index'); } else {
Redirect::to('register');
}
} }
/** /**

View File

@@ -15,15 +15,13 @@ class RegistrationModel
*/ */
public static function registerNewUser($isAdmin = false) public static function registerNewUser($isAdmin = false)
{ {
// clean the input
$user_name = strip_tags(Request::post('user_name')); $user_name = strip_tags(Request::post('user_name'));
$user_email = strip_tags(Request::post('user_email')); $user_email = strip_tags(Request::post('user_email'));
// Use 'user_password' if provided (admin registration), otherwise 'user_password_new' $user_password_new = Request::post('user_password_new');
$user_password_new = $isAdmin ? Request::post('user_password_new') : Request::post('user_password_new'); $user_password_repeat = $user_password_new;
$user_password_repeat = $user_password_new; // no repeat field
// validate using existing validators and messages
$valid = true; $valid = true;
if (!self::validateRecaptcha()) { $valid = false; }
if (!self::validateUserName($user_name)) { $valid = false; } if (!self::validateUserName($user_name)) { $valid = false; }
if (!self::validateUserEmail($user_email, $user_email)) { $valid = false; } if (!self::validateUserEmail($user_email, $user_email)) { $valid = false; }
if (!self::validateUserPassword($user_password_new, $user_password_repeat)) { $valid = false; } if (!self::validateUserPassword($user_password_new, $user_password_repeat)) { $valid = false; }
@@ -77,12 +75,35 @@ class RegistrationModel
return $return; return $return;
} }
/** public static function validateRecaptcha()
* Validates the username {
* $recaptcha_response = Request::post('g-recaptcha-response');
* @param $user_name
* @return bool if (empty($recaptcha_response)) {
*/ Session::add('feedback_negative', 'reCAPTCHA verification failed. Please try again.');
return false;
}
$secret_key = Config::get('RECAPTCHA_SECRET_KEY');
$verify_url = 'https://www.google.com/recaptcha/api/siteverify';
$response = file_get_contents($verify_url . '?secret=' . $secret_key . '&response=' . $recaptcha_response);
$response_data = json_decode($response);
if (!$response_data->success) {
Session::add('feedback_negative', 'reCAPTCHA verification failed. Please try again.');
return false;
}
// v3 returns a score from 0.0 to 1.0 (1.0 = likely human, 0.0 = likely bot)
if (isset($response_data->score) && $response_data->score < 0.5) {
Session::add('feedback_negative', 'Registration blocked due to suspicious activity.');
return false;
}
return true;
}
public static function validateUserName($user_name) public static function validateUserName($user_name)
{ {
if (empty($user_name)) { if (empty($user_name)) {
@@ -90,7 +111,6 @@ class RegistrationModel
return false; return false;
} }
// if username is too short (2), too long (64) or does not fit the pattern (aZ09)
if (!preg_match('/^[a-zA-Z0-9]{2,64}$/', $user_name)) { if (!preg_match('/^[a-zA-Z0-9]{2,64}$/', $user_name)) {
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN')); Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN'));
return false; return false;

View File

@@ -37,6 +37,9 @@
<div class="link-forgot-my-password"> <div class="link-forgot-my-password">
<a href="<?php echo Config::get('URL'); ?>login/requestPasswordReset">I forgot my password</a> <a href="<?php echo Config::get('URL'); ?>login/requestPasswordReset">I forgot my password</a>
</div> </div>
<div class="link-register" style="margin-top: 15px;">
<a href="<?php echo Config::get('URL'); ?>register" class="button">Register new account</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,18 +1,30 @@
<div class="container"> <script src="https://www.google.com/recaptcha/api.js?render=<?php echo Config::get('RECAPTCHA_SITE_KEY'); ?>"></script>
<!-- echo out the system feedback (error and success messages) --> <div class="container">
<?php $this->renderFeedbackMessages(); ?> <?php $this->renderFeedbackMessages(); ?>
<!-- login box on left side -->
<div class="login-box" style="width: 50%; display: block;"> <div class="login-box" style="width: 50%; display: block;">
<h2>Register a new account</h2> <h2>Register a new account</h2>
<!-- register form --> <form method="post" action="<?php echo Config::get('URL'); ?>register/register_action" id="register-form">
<form method="post" action="<?php echo Config::get('URL'); ?>register/register_action">
<input type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" placeholder="Username (letters/numbers, 2-64 chars)" required /> <input type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" placeholder="Username (letters/numbers, 2-64 chars)" required />
<input type="text" name="user_email" placeholder="email address (a real address)" required /> <input type="text" name="user_email" placeholder="email address (a real address)" required />
<input type="password" name="user_password_new" pattern=".{6,}" placeholder="Password (6+ characters)" required autocomplete="off" /> <input type="password" name="user_password_new" pattern=".{6,}" placeholder="Password (6+ characters)" required autocomplete="off" />
<input type="hidden" name="g-recaptcha-response" id="recaptcha-response" />
<input type="submit" value="Register" /> <input type="submit" value="Register" />
</form> </form>
</div> </div>
</div> </div>
<script>
document.getElementById('register-form').addEventListener('submit', function(e) {
e.preventDefault();
var form = this;
grecaptcha.ready(function() {
grecaptcha.execute('<?php echo Config::get('RECAPTCHA_SITE_KEY'); ?>', {action: 'register'}).then(function(token) {
document.getElementById('recaptcha-response').value = token;
form.submit();
});
});
});
</script>