Skip to content

Commit d6f7ff8

Browse files
TitaKolevatkcreateit
authored andcommitted
OXDEV-10037 Handle resend otp response
1 parent cc235a5 commit d6f7ff8

9 files changed

Lines changed: 125 additions & 43 deletions

File tree

assets/out/src/js/module/resend-otp.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,16 @@ export class ResendOtp {
4848
this.lock(this.textSending);
4949

5050
try {
51-
await fetch(this.url, {
51+
const response = await fetch(this.url, {
5252
method: 'POST',
5353
headers: { 'Content-Type': 'application/json' }
5454
});
5555

56+
if (!response.ok) {
57+
this.unlock();
58+
return;
59+
}
60+
5661
const until = Date.now() + this.cooldownSeconds * 1000;
5762
this.storeUntil(until);
5863
this.startCooldown(until);

src/Authentication/TwoFactorAuth/Controller/TwoFactorAuthController.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Service\AuthorizeServiceInterface;
1616
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Service\UserServiceInterface;
1717
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\AuthCodeRequestInterface;
18+
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\JsonResponseInterface;
1819

1920
class TwoFactorAuthController extends FrontendController
2021
{
@@ -31,6 +32,7 @@ public function __construct(
3132
private readonly UserServiceInterface $userService,
3233
private readonly AuthCodeRequestInterface $authCodeRequest,
3334
private readonly UtilsView $utilsView,
35+
private readonly JsonResponseInterface $jsonResponse,
3436
) {
3537
parent::__construct();
3638
}
@@ -52,6 +54,12 @@ public function handleOTP(): ?string
5254

5355
public function resendCode(): void
5456
{
55-
$this->authService->generate();
57+
$success = $this->authService->resend();
58+
59+
if (!$success) {
60+
$this->jsonResponse->setStatusCode(429);
61+
}
62+
63+
$this->jsonResponse->send(['success' => $success]);
5664
}
5765
}

src/Authentication/TwoFactorAuth/Service/AuthorizeService.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,25 @@ public function generate(): void
4848
);
4949

5050
$userId = $this->session->get(self::USER_SESSION_KEY);
51-
if (!$this->resendOTPService->canSend($userId)) {
52-
return;
53-
}
5451

5552
$OTPCode = $verificator->generate($userId);
5653

5754
$user = $this->userRepository->getUserOTPData($userId);
5855
$notifier = $this->notifierCollector->getNotifier('email');
5956
$notifier->notify($user->getEmail(), $OTPCode);
57+
}
58+
59+
public function resend(): bool
60+
{
61+
$userId = $this->session->get(self::USER_SESSION_KEY);
62+
if (!$this->resendOTPService->canSend($userId)) {
63+
return false;
64+
}
65+
66+
$this->generate();
6067
$this->resendOTPService->markAsSent($userId);
68+
69+
return true;
6170
}
6271

6372
public function getVerificationUrl(): string

src/Authentication/TwoFactorAuth/Service/AuthorizeServiceInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public function validate(#[\SensitiveParameter] string $inputCode): void;
1313

1414
public function generate(): void;
1515

16+
public function resend(): bool;
17+
1618
public function getVerificationUrl(): string;
1719

1820
public function getRemainingAttempts(): int;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/**
4+
* Copyright © OXID eSales AG. All rights reserved.
5+
* See LICENSE file for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput;
11+
12+
use OxidEsales\Eshop\Core\Utils;
13+
14+
class JsonResponse implements JsonResponseInterface
15+
{
16+
private int $statusCode = 200;
17+
18+
public function __construct(
19+
private readonly Utils $utils
20+
) {
21+
}
22+
23+
public function setStatusCode(int $code): void
24+
{
25+
$this->statusCode = $code;
26+
}
27+
28+
public function send(array $data): void
29+
{
30+
$this->utils->setHeader('HTTP/1.1 ' . $this->statusCode);
31+
$this->utils->setHeader('Content-Type: application/json');
32+
$this->utils->showMessageAndExit(json_encode($data));
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
/**
4+
* Copyright © OXID eSales AG. All rights reserved.
5+
* See LICENSE file for license details.
6+
*/
7+
8+
namespace OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput;
9+
10+
interface JsonResponseInterface
11+
{
12+
public function setStatusCode(int $code): void;
13+
14+
public function send(array $data): void;
15+
}

src/Authentication/TwoFactorAuth/Transput/services.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ services:
22
_defaults:
33
autowire: true
44
public: false
5+
bind:
6+
OxidEsales\Eshop\Core\Utils: '@=service("OxidEsales\\SecurityModule\\Core\\Registry").getUtils()'
57

68
OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\AuthCodeRequestInterface:
79
class: OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\AuthCodeRequest
810
public: true
11+
12+
OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\JsonResponseInterface:
13+
class: OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\JsonResponse
14+
public: true

tests/Unit/Authentication/TwoFactorAuth/Controller/TwoFactorAuthControllerTest.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Service\AuthorizeServiceInterface;
1616
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Service\UserServiceInterface;
1717
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\AuthCodeRequestInterface;
18+
use OxidEsales\SecurityModule\Authentication\TwoFactorAuth\Transput\JsonResponseInterface;
1819
use PHPUnit\Framework\TestCase;
1920

2021
class TwoFactorAuthControllerTest extends TestCase
@@ -115,14 +116,46 @@ public function testHandleOTPPropagatesNonOTPExceptions(): void
115116
$controller->handleOTP();
116117
}
117118

118-
public function testResendCodeCallsGenerate(): void
119+
public function testResendCodeSendsSuccessResponse(): void
119120
{
120121
$authServiceMock = $this->createMock(AuthorizeServiceInterface::class);
121122
$authServiceMock->expects($this->once())
122-
->method('generate');
123+
->method('resend')
124+
->willReturn(true);
125+
126+
$jsonResponseMock = $this->createMock(JsonResponseInterface::class);
127+
$jsonResponseMock->expects($this->never())
128+
->method('setStatusCode');
129+
$jsonResponseMock->expects($this->once())
130+
->method('send')
131+
->with(['success' => true]);
132+
133+
$controller = $this->getSut(
134+
authService: $authServiceMock,
135+
jsonResponse: $jsonResponseMock,
136+
);
137+
138+
$controller->resendCode();
139+
}
140+
141+
public function testResendCodeSends429WhenCooldownActive(): void
142+
{
143+
$authServiceMock = $this->createMock(AuthorizeServiceInterface::class);
144+
$authServiceMock->expects($this->once())
145+
->method('resend')
146+
->willReturn(false);
147+
148+
$jsonResponseMock = $this->createMock(JsonResponseInterface::class);
149+
$jsonResponseMock->expects($this->once())
150+
->method('setStatusCode')
151+
->with(429);
152+
$jsonResponseMock->expects($this->once())
153+
->method('send')
154+
->with(['success' => false]);
123155

124156
$controller = $this->getSut(
125157
authService: $authServiceMock,
158+
jsonResponse: $jsonResponseMock,
126159
);
127160

128161
$controller->resendCode();
@@ -133,12 +166,14 @@ private function getSut(
133166
UserServiceInterface $userService = null,
134167
AuthCodeRequestInterface $authCodeRequest = null,
135168
UtilsView $utilsView = null,
169+
JsonResponseInterface $jsonResponse = null,
136170
): TwoFactorAuthController {
137171
return new TwoFactorAuthController(
138172
authService: $authService ?? $this->createStub(AuthorizeServiceInterface::class),
139173
userService: $userService ?? $this->createStub(UserServiceInterface::class),
140174
authCodeRequest: $authCodeRequest ?? $this->createStub(AuthCodeRequestInterface::class),
141175
utilsView: $utilsView ?? $this->createStub(UtilsView::class),
176+
jsonResponse: $jsonResponse ?? $this->createStub(JsonResponseInterface::class),
142177
);
143178
}
144179
}

tests/Unit/Authentication/TwoFactorAuth/Service/AuthorizeServiceTest.php

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,6 @@ public function testGenerateNotifiesUser(): void
141141
->method('notify')
142142
->with($userEmail, $generatedCode);
143143

144-
$resendOTPStub = $this->createStub(ResendOTPServiceInterface::class);
145-
$resendOTPStub->method('canSend')->willReturn(true);
146-
147144
$userStub = $this->createStub(UserInterface::class);
148145
$userStub->method('getEmail')->willReturn($userEmail);
149146

@@ -174,15 +171,14 @@ public function testGenerateNotifiesUser(): void
174171
moduleSettings: $settingsStub,
175172
verifyCollector: $collectorStub,
176173
notifierCollector: $notifierCollectorStub,
177-
resendOTPService: $resendOTPStub,
178174
userRepository: $userRepositoryStub,
179175
session: $sessionStub
180176
);
181177

182178
$sut->generate();
183179
}
184180

185-
public function testGenerateSendsOtpAndMarksAsSent(): void
181+
public function testResendSendsOtpAndMarksAsSent(): void
186182
{
187183
$userEmail = 'user@example.com';
188184

@@ -223,21 +219,11 @@ public function testGenerateSendsOtpAndMarksAsSent(): void
223219
session: $sessionStub
224220
);
225221

226-
$sut->generate();
222+
$this->assertTrue($sut->resend());
227223
}
228224

229-
public function testGenerateDoesNothingWhenCannotSend(): void
225+
public function testResendReturnsFalseWhenCooldownActive(): void
230226
{
231-
$verificatorMock = $this->createMock(VerificatorAdapterInterface::class);
232-
$verificatorMock
233-
->expects($this->never())
234-
->method('generate');
235-
236-
$notifierMock = $this->createMock(NotifierAdapterInterface::class);
237-
$notifierMock
238-
->expects($this->never())
239-
->method('notify');
240-
241227
$resendOTPMock = $this->createMock(ResendOTPServiceInterface::class);
242228
$resendOTPMock
243229
->method('canSend')
@@ -246,35 +232,17 @@ public function testGenerateDoesNothingWhenCannotSend(): void
246232
->expects($this->never())
247233
->method('markAsSent');
248234

249-
$settingsStub = $this->createStub(ModuleSettingsServiceInterface::class);
250-
$settingsStub
251-
->method('getTwoFactorAuthType')
252-
->willReturn('otp');
253-
254-
$collectorStub = $this->createStub(VerificationCollectorServiceInterface::class);
255-
$collectorStub
256-
->method('getVerificator')
257-
->willReturn($verificatorMock);
258-
259-
$notifierCollectorStub = $this->createStub(NotifierCollectorInterface::class);
260-
$notifierCollectorStub
261-
->method('getNotifier')
262-
->willReturn($notifierMock);
263-
264235
$sessionStub = $this->createStub(SessionInterface::class);
265236
$sessionStub
266237
->method('get')
267238
->willReturn(uniqid());
268239

269240
$sut = $this->getSut(
270-
moduleSettings: $settingsStub,
271-
verifyCollector: $collectorStub,
272-
notifierCollector: $notifierCollectorStub,
273241
resendOTPService: $resendOTPMock,
274242
session: $sessionStub
275243
);
276244

277-
$sut->generate();
245+
$this->assertFalse($sut->resend());
278246
}
279247

280248
public function testGetRemainingAttemptsReturnsValueFromVerificator(): void

0 commit comments

Comments
 (0)