Skip to content

Commit 44c792d

Browse files
chore: Add PR's requested changes
1 parent 14abc2f commit 44c792d

2 files changed

Lines changed: 18 additions & 31 deletions

File tree

app/Services/TurnstileClient.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<?php namespace App\Services;
1+
<?php
2+
namespace App\Services;
23
/**
34
* Copyright 2026 OpenStack Foundation
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,31 +24,33 @@
2324
*/
2425
final class TurnstileClient implements ClientInterface
2526
{
26-
public function __construct(private string $secret) {}
27+
public function __construct(private string $secret)
28+
{
29+
}
2730

2831
public function siteverify(string $token): SiteverifyResponse
2932
{
3033
$response = Http::retry(3, 100)
3134
->asForm()
3235
->acceptJson()
3336
->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
34-
'secret' => $this->secret,
37+
'secret' => $this->secret,
3538
'response' => $token,
3639
]);
3740

38-
if (! $response->ok()) {
41+
if (!$response->ok()) {
3942
return SiteverifyResponse::failure(['http-error']);
4043
}
4144

4245
if ($response->json('success') === true) {
4346
return SiteverifyResponse::success();
4447
}
4548

46-
return SiteverifyResponse::failure($response->json('error-codes') ?? []);
49+
return SiteverifyResponse::failure($response->json('error-codes') ?: ['internal-error']);
4750
}
4851

4952
public function dummy(): string
5053
{
5154
return self::RESPONSE_DUMMY_TOKEN;
5255
}
53-
}
56+
}

tests/UserLoginTurnstileTest.php

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<?php namespace Tests;
1+
<?php
2+
namespace Tests;
23
/**
34
* Copyright 2026 OpenStack Foundation
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,23 +25,23 @@
2425
* - cf-turnstile-response required when login_attempts (from request body) >= threshold
2526
* - threshold gating (before / at boundary / above boundary)
2627
* - omitted login_attempts field defaults to zero (no captcha required)
27-
* - captcha is gated on the request-body counter, not on DB state
28+
* - captcha is gated on the request-body counter
2829
* - login screen emits Turnstile JS config after a failed attempt
2930
* - expired or unsolved token is rejected
3031
*/
3132
final class UserLoginTurnstileTest extends BrowserKitTestCase
3233
{
3334
private const LOGIN_URL = '/auth/login';
3435
// Matches ServerConfigurationService::DefaultMaxFailedLoginAttempts2ShowCaptcha
35-
private const CAPTCHA_THRESHOLD = 3;
36+
private const CAPTCHA_THRESHOLD = 3;
3637

3738
private string $testEmail;
3839
private string $testPassword;
3940

4041
protected function prepareForTests(): void
4142
{
4243
parent::prepareForTests();
43-
$this->testEmail = env('TEST_USER_EMAIL');
44+
$this->testEmail = env('TEST_USER_EMAIL');
4445
$this->testPassword = env('TEST_USER_PASSWORD');
4546
if (empty($this->testEmail) || empty($this->testPassword)) {
4647
$this->markTestSkipped('TEST_USER_EMAIL and TEST_USER_PASSWORD env vars are required.');
@@ -67,8 +68,8 @@ private function postLogin(array $overrides = [])
6768
return $this->call('POST', self::LOGIN_URL, array_merge([
6869
'username' => $this->testEmail,
6970
'password' => $this->testPassword,
70-
'flow' => 'password',
71-
'_token' => Session::token(),
71+
'flow' => 'password',
72+
'_token' => Session::token(),
7273
], $overrides));
7374
}
7475

@@ -138,7 +139,7 @@ public function testLoginAtThresholdWithValidTokenPassesValidation(): void
138139

139140
$this->postLogin([
140141
'cf-turnstile-response' => 'dummy-token-accepted-by-mock',
141-
'login_attempts' => 1
142+
'login_attempts' => self::CAPTCHA_THRESHOLD
142143
]);
143144

144145
$this->assertFalse(
@@ -162,23 +163,6 @@ public function testOmittedLoginAttemptsFieldDefaultsToZeroNoCaptchaRequired():
162163
);
163164
}
164165

165-
public function testCaptchaGatingUsesRequestBodyCounterNotDbState(): void
166-
{
167-
// Even for a username that doesn't exist in the DB, if the request body
168-
// carries login_attempts >= threshold the captcha rule must fire.
169-
// This proves gating is driven by the request-body counter, not by DB lookup.
170-
$this->postLogin([
171-
'username' => 'nobody@doesnotexist.example',
172-
'password' => 'irrelevant',
173-
'login_attempts' => self::CAPTCHA_THRESHOLD,
174-
]); // no cf-turnstile-response
175-
176-
$this->assertTrue(
177-
$this->sessionHasValidationError('cf-turnstile-response'),
178-
'Turnstile must be required when login_attempts >= threshold, regardless of whether the user exists'
179-
);
180-
}
181-
182166
// -------------------------------------------------------------------------
183167
// 4. Rendering of Turnstile on the thresholded login screen
184168
// -------------------------------------------------------------------------
@@ -241,4 +225,4 @@ public function testUnsolvedCaptchaEmptyTokenFailsValidation(): void
241225
'An empty Turnstile response must be rejected by the required rule'
242226
);
243227
}
244-
}
228+
}

0 commit comments

Comments
 (0)