Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.00% covered (success)
84.00%
21 / 25
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
LoginController
84.00% covered (success)
84.00%
21 / 25
50.00% covered (danger)
50.00%
1 / 2
5.10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 login
81.82% covered (success)
81.82%
18 / 22
0.00% covered (danger)
0.00%
0 / 1
3.05
1<?php
2
3/**
4 * The Login Controller for the REST API
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2023-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2023-07-30
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Api;
21
22use JsonException;
23use OpenApi\Attributes as OA;
24use phpMyFAQ\Controller\AbstractController;
25use phpMyFAQ\Core\Exception;
26use phpMyFAQ\Filter;
27use phpMyFAQ\Translation;
28use phpMyFAQ\User\CurrentUser;
29use phpMyFAQ\User\UserAuthentication;
30use Symfony\Component\HttpFoundation\JsonResponse;
31use Symfony\Component\HttpFoundation\Request;
32use Symfony\Component\HttpFoundation\Response;
33use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
34use Symfony\Component\Routing\Attribute\Route;
35
36final class LoginController extends AbstractController
37{
38    public function __construct()
39    {
40        parent::__construct();
41
42        if (!$this->isApiEnabled()) {
43            throw new UnauthorizedHttpException(challenge: 'API is not enabled');
44        }
45    }
46
47    /**
48     * @throws JsonException|Exception
49     */
50    #[OA\Post(path: '/api/v4.0/login', operationId: 'login', tags: ['Public Endpoints'])]
51    #[OA\Header(
52        header: 'Accept-Language',
53        description: 'The language code for the login.',
54        schema: new OA\Schema(type: 'string'),
55    )]
56    #[OA\RequestBody(
57        description: 'The username and password for the login.',
58        required: true,
59        content: new OA\MediaType(
60            mediaType: 'application/json',
61            schema: new OA\Schema(
62                required: ['username', 'password'],
63                properties: [
64                    new OA\Property(property: 'username', type: 'string'),
65                    new OA\Property(property: 'password', type: 'string'),
66                ],
67                type: 'object',
68            ),
69        ),
70    )]
71    #[OA\Response(
72        response: 200,
73        description: 'If "username" and "password" combination are correct.',
74        content: new OA\JsonContent(example: ['loggedin' => true]),
75    )]
76    #[OA\Response(
77        response: 400,
78        description: 'If "username" and "password" combination are wrong.',
79        content: new OA\JsonContent(example: ['loggedin' => false, 'error' => 'Wrong username or password.']),
80    )]
81    #[Route(path: 'v4.0/login', name: 'api.login', methods: ['POST'])]
82    public function login(Request $request): JsonResponse
83    {
84        $data = json_decode(json: $request->getContent(), associative: false, depth: 512, flags: JSON_THROW_ON_ERROR);
85        if (!$data instanceof \stdClass) {
86            return $this->json([
87                'loggedin' => false,
88                'error' => 'The request body must be a JSON object.',
89            ], Response::HTTP_BAD_REQUEST);
90        }
91
92        $faqUsername = Filter::filterVar($data->username ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
93        $faqPassword = Filter::filterVar($data->password ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
94
95        $user = new CurrentUser($this->configuration);
96        $userAuthentication = new UserAuthentication($this->configuration, $user, $this->getRateLimiter());
97        try {
98            $user = $userAuthentication->authenticate($faqUsername, $faqPassword);
99            $result = [
100                'loggedin' => $user->isLoggedIn(),
101            ];
102            return $this->json($result, Response::HTTP_OK);
103        } catch (Exception $exception) {
104            $this->configuration->getLogger()->error('Failed login: ' . $exception->getMessage());
105            $result = [
106                'loggedin' => $user->isLoggedIn(),
107                'error' => Translation::get(key: 'ad_auth_fail'),
108            ];
109            return $this->json($result, Response::HTTP_BAD_REQUEST);
110        }
111    }
112}