Passed
Push — trunk ( 1e1758...8e4416 )
by Christian
12:44 queued 14s
created

AuthController::resetPassword()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
nc 10
nop 2
dl 0
loc 35
rs 8.9457
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Storefront\Controller;
4
5
use Shopware\Core\Checkout\Customer\Exception\BadCredentialsException;
6
use Shopware\Core\Checkout\Customer\Exception\CustomerAuthThrottledException;
7
use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundByHashException;
8
use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundException;
9
use Shopware\Core\Checkout\Customer\Exception\CustomerOptinNotCompletedException;
10
use Shopware\Core\Checkout\Customer\Exception\CustomerRecoveryHashExpiredException;
11
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLoginRoute;
12
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
13
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractResetPasswordRoute;
14
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractSendPasswordRecoveryMailRoute;
15
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
16
use Shopware\Core\Framework\Log\Package;
17
use Shopware\Core\Framework\RateLimiter\Exception\RateLimitExceededException;
18
use Shopware\Core\Framework\Routing\RoutingException;
19
use Shopware\Core\Framework\Validation\DataBag\DataBag;
20
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
21
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
22
use Shopware\Core\PlatformRequest;
23
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
24
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
25
use Shopware\Core\System\SalesChannel\SalesChannelContext;
26
use Shopware\Storefront\Checkout\Cart\SalesChannel\StorefrontCartFacade;
27
use Shopware\Storefront\Framework\Routing\RequestTransformer;
28
use Shopware\Storefront\Page\Account\Login\AccountGuestLoginPageLoadedHook;
29
use Shopware\Storefront\Page\Account\Login\AccountLoginPageLoadedHook;
30
use Shopware\Storefront\Page\Account\Login\AccountLoginPageLoader;
31
use Shopware\Storefront\Page\Account\RecoverPassword\AccountRecoverPasswordPageLoadedHook;
32
use Shopware\Storefront\Page\Account\RecoverPassword\AccountRecoverPasswordPageLoader;
33
use Symfony\Component\HttpFoundation\Request;
34
use Symfony\Component\HttpFoundation\Response;
35
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
36
use Symfony\Component\Routing\Annotation\Route;
37
38
/**
39
 * @internal
40
 * Do not use direct or indirect repository calls in a controller. Always use a store-api route to get or put data
41
 */
42
#[Route(defaults: ['_routeScope' => ['storefront']])]
43
#[Package('storefront')]
44
class AuthController extends StorefrontController
45
{
46
    /**
47
     * @internal
48
     */
49
    public function __construct(
50
        private readonly AccountLoginPageLoader $loginPageLoader,
51
        private readonly AbstractSendPasswordRecoveryMailRoute $sendPasswordRecoveryMailRoute,
52
        private readonly AbstractResetPasswordRoute $resetPasswordRoute,
53
        private readonly AbstractLoginRoute $loginRoute,
54
        private readonly AbstractLogoutRoute $logoutRoute,
55
        private readonly StorefrontCartFacade $cartFacade,
56
        private readonly AccountRecoverPasswordPageLoader $recoverPasswordPageLoader,
57
        private readonly SalesChannelContextServiceInterface $salesChannelContextService
58
    ) {
59
    }
60
61
    #[Route(path: '/account/login', name: 'frontend.account.login.page', defaults: ['_noStore' => true], methods: ['GET'])]
62
    public function loginPage(Request $request, RequestDataBag $data, SalesChannelContext $context): Response
63
    {
64
        /** @var string $redirect */
65
        $redirect = $request->get('redirectTo', 'frontend.account.home.page');
66
67
        $customer = $context->getCustomer();
68
69
        if ($customer !== null && $customer->getGuest() === false) {
70
            $request->request->set('redirectTo', $redirect);
71
72
            return $this->createActionResponse($request);
73
        }
74
75
        $page = $this->loginPageLoader->load($request, $context);
76
77
        $this->hook(new AccountLoginPageLoadedHook($page, $context));
78
79
        return $this->renderStorefront('@Storefront/storefront/page/account/register/index.html.twig', [
80
            'redirectTo' => $redirect,
81
            'redirectParameters' => $request->get('redirectParameters', json_encode([])),
82
            'errorRoute' => $request->attributes->get('_route'),
83
            'page' => $page,
84
            'loginError' => (bool) $request->get('loginError'),
85
            'waitTime' => $request->get('waitTime'),
86
            'errorSnippet' => $request->get('errorSnippet'),
87
            'data' => $data,
88
        ]);
89
    }
90
91
    #[Route(path: '/account/guest/login', name: 'frontend.account.guest.login.page', defaults: ['_noStore' => true], methods: ['GET'])]
92
    public function guestLoginPage(Request $request, SalesChannelContext $context): Response
93
    {
94
        /** @var string $redirect */
95
        $redirect = $request->get('redirectTo', 'frontend.account.home.page');
96
97
        $customer = $context->getCustomer();
98
99
        if ($customer !== null) {
100
            $request->request->set('redirectTo', $redirect);
101
102
            return $this->createActionResponse($request);
103
        }
104
105
        $waitTime = (int) $request->get('waitTime');
106
        if ($waitTime) {
107
            $this->addFlash(self::INFO, $this->trans('account.loginThrottled', ['%seconds%' => $waitTime]));
108
        }
109
110
        if ((bool) $request->get('loginError')) {
111
            $this->addFlash(self::DANGER, $this->trans('account.orderGuestLoginWrongCredentials'));
112
        }
113
114
        $page = $this->loginPageLoader->load($request, $context);
115
116
        $this->hook(new AccountGuestLoginPageLoadedHook($page, $context));
117
118
        return $this->renderStorefront('@Storefront/storefront/page/account/guest-auth.html.twig', [
119
            'redirectTo' => $redirect,
120
            'redirectParameters' => $request->get('redirectParameters', json_encode([])),
121
            'page' => $page,
122
        ]);
123
    }
124
125
    #[Route(path: '/account/logout', name: 'frontend.account.logout.page', methods: ['GET'])]
126
    public function logout(Request $request, SalesChannelContext $context, RequestDataBag $dataBag): Response
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

126
    public function logout(/** @scrutinizer ignore-unused */ Request $request, SalesChannelContext $context, RequestDataBag $dataBag): Response

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
127
    {
128
        if ($context->getCustomer() === null) {
129
            return $this->redirectToRoute('frontend.account.login.page');
130
        }
131
132
        try {
133
            $this->logoutRoute->logout($context, $dataBag);
134
            $this->addFlash(self::SUCCESS, $this->trans('account.logoutSucceeded'));
135
136
            $parameters = [];
137
        } catch (ConstraintViolationException $formViolations) {
138
            $parameters = ['formViolations' => $formViolations];
139
        }
140
141
        return $this->redirectToRoute('frontend.account.login.page', $parameters);
142
    }
143
144
    #[Route(path: '/account/login', name: 'frontend.account.login', defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
145
    public function login(Request $request, RequestDataBag $data, SalesChannelContext $context): Response
146
    {
147
        $customer = $context->getCustomer();
148
149
        if ($customer !== null && $customer->getGuest() === false) {
150
            return $this->createActionResponse($request);
151
        }
152
153
        try {
154
            $token = $this->loginRoute->login($data, $context)->getToken();
155
            $cartBeforeNewContext = $this->cartFacade->get($token, $context);
156
157
            $newContext = $this->salesChannelContextService->get(
158
                new SalesChannelContextServiceParameters(
159
                    $context->getSalesChannelId(),
160
                    $token,
161
                    $context->getLanguageIdChain()[0],
162
                    $context->getCurrencyId(),
163
                    $context->getDomainId(),
164
                    $context->getContext()
165
                )
166
            );
167
168
            // Update the sales channel context for CacheResponseSubscriber
169
            $request->attributes->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT, $newContext);
170
171
            if (!empty($token)) {
172
                $this->addCartErrors($cartBeforeNewContext);
173
174
                return $this->createActionResponse($request);
175
            }
176
        } catch (BadCredentialsException|UnauthorizedHttpException|CustomerOptinNotCompletedException|CustomerAuthThrottledException $e) {
177
            if ($e instanceof CustomerOptinNotCompletedException) {
178
                $errorSnippet = $e->getSnippetKey();
179
            }
180
181
            if ($e instanceof CustomerAuthThrottledException) {
182
                $waitTime = $e->getWaitTime();
183
            }
184
        }
185
186
        $data->set('password', null);
187
188
        return $this->forwardToRoute(
189
            'frontend.account.login.page',
190
            [
191
                'loginError' => true,
192
                'errorSnippet' => $errorSnippet ?? null,
193
                'waitTime' => $waitTime ?? null,
194
            ]
195
        );
196
    }
197
198
    #[Route(path: '/account/recover', name: 'frontend.account.recover.page', methods: ['GET'])]
199
    public function recoverAccountForm(Request $request, SalesChannelContext $context): Response
200
    {
201
        $page = $this->loginPageLoader->load($request, $context);
202
203
        return $this->renderStorefront('@Storefront/storefront/page/account/profile/recover-password.html.twig', [
204
            'page' => $page,
205
        ]);
206
    }
207
208
    #[Route(path: '/account/recover', name: 'frontend.account.recover.request', methods: ['POST'])]
209
    public function generateAccountRecovery(Request $request, RequestDataBag $data, SalesChannelContext $context): Response
210
    {
211
        try {
212
            $mailData = $data->get('email');
213
            if (!$mailData instanceof DataBag) {
214
                throw RoutingException::invalidRequestParameter('email');
215
            }
216
            $mailData->set('storefrontUrl', $request->attributes->get(RequestTransformer::STOREFRONT_URL));
217
218
            $this->sendPasswordRecoveryMailRoute->sendRecoveryMail(
219
                $mailData->toRequestDataBag(),
220
                $context,
221
                false
222
            );
223
224
            $this->addFlash(self::SUCCESS, $this->trans('account.recoveryMailSend'));
225
        } catch (CustomerNotFoundException $e) {
226
            $this->addFlash(self::SUCCESS, $this->trans('account.recoveryMailSend'));
227
        } catch (InconsistentCriteriaIdsException $e) {
228
            $this->addFlash(self::DANGER, $this->trans('error.message-default'));
229
        } catch (RateLimitExceededException $e) {
230
            $this->addFlash(self::INFO, $this->trans('error.rateLimitExceeded', ['%seconds%' => $e->getWaitTime()]));
231
        }
232
233
        return $this->redirectToRoute('frontend.account.recover.page');
234
    }
235
236
    #[Route(path: '/account/recover/password', name: 'frontend.account.recover.password.page', methods: ['GET'])]
237
    public function resetPasswordForm(Request $request, SalesChannelContext $context): Response
238
    {
239
        /** @var ?string $hash */
240
        $hash = $request->get('hash');
241
242
        if (!$hash || !\is_string($hash)) {
243
            $this->addFlash(self::DANGER, $this->trans('account.passwordHashNotFound'));
244
245
            return $this->redirectToRoute('frontend.account.recover.request');
246
        }
247
248
        try {
249
            $page = $this->recoverPasswordPageLoader->load($request, $context, $hash);
250
        } catch (ConstraintViolationException) {
251
            $this->addFlash(self::DANGER, $this->trans('account.passwordHashNotFound'));
252
253
            return $this->redirectToRoute('frontend.account.recover.request');
254
        }
255
256
        $this->hook(new AccountRecoverPasswordPageLoadedHook($page, $context));
257
258
        if ($page->getHash() === null || $page->isHashExpired()) {
259
            $this->addFlash(self::DANGER, $this->trans('account.passwordHashNotFound'));
260
261
            return $this->redirectToRoute('frontend.account.recover.request');
262
        }
263
264
        return $this->renderStorefront('@Storefront/storefront/page/account/profile/reset-password.html.twig', [
265
            'page' => $page,
266
            'formViolations' => $request->get('formViolations'),
267
        ]);
268
    }
269
270
    #[Route(path: '/account/recover/password', name: 'frontend.account.recover.password.reset', methods: ['POST'])]
271
    public function resetPassword(RequestDataBag $data, SalesChannelContext $context): Response
272
    {
273
        $passwordData = $data->get('password');
274
        if (!$passwordData instanceof DataBag) {
275
            throw RoutingException::invalidRequestParameter('password');
276
        }
277
        $hash = $passwordData->get('hash');
278
279
        try {
280
            $this->resetPasswordRoute->resetPassword($passwordData->toRequestDataBag(), $context);
281
282
            $this->addFlash(self::SUCCESS, $this->trans('account.passwordChangeSuccess'));
283
        } catch (ConstraintViolationException $formViolations) {
284
            $this->addFlash(self::DANGER, $this->trans('account.passwordChangeNoSuccess'));
285
286
            if ($formViolations->getViolations('newPassword')->count() === 1) {
287
                $this->addFlash(self::DANGER, $this->trans('account.passwordNotIdentical'));
288
            }
289
290
            return $this->forwardToRoute(
291
                'frontend.account.recover.password.page',
292
                ['hash' => $hash, 'formViolations' => $formViolations, 'passwordFormViolation' => true]
293
            );
294
        } catch (CustomerNotFoundByHashException) {
295
            $this->addFlash(self::DANGER, $this->trans('account.passwordChangeNoSuccess'));
296
297
            return $this->forwardToRoute('frontend.account.recover.request');
298
        } catch (CustomerRecoveryHashExpiredException) {
299
            $this->addFlash(self::DANGER, $this->trans('account.passwordHashExpired'));
300
301
            return $this->forwardToRoute('frontend.account.recover.request');
302
        }
303
304
        return $this->redirectToRoute('frontend.account.profile.page');
305
    }
306
}
307