Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.43% covered (warning)
71.43%
20 / 28
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
PushController
71.43% covered (warning)
71.43%
20 / 28
0.00% covered (danger)
0.00%
0 / 1
5.58
0.00% covered (danger)
0.00%
0 / 1
 generateVapidKeys
71.43% covered (warning)
71.43%
20 / 28
0.00% covered (danger)
0.00%
0 / 1
5.58
1<?php
2
3/**
4 * The Push Notification Controller for the Admin 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 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     2026-02-02
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Administration\Api;
21
22use phpMyFAQ\Enums\PermissionType;
23use phpMyFAQ\Push\WebPushService;
24use phpMyFAQ\Session\Token;
25use phpMyFAQ\Translation;
26use Symfony\Component\HttpFoundation\JsonResponse;
27use Symfony\Component\HttpFoundation\Request;
28use Symfony\Component\HttpFoundation\Response;
29use Symfony\Component\Routing\Attribute\Route;
30
31final class PushController extends AbstractAdministrationApiController
32{
33    /**
34     * Generates a new VAPID key pair and saves it to configuration.
35     */
36    #[Route(path: 'push/generate-vapid-keys', name: 'admin.api.push.generate-vapid-keys', methods: ['POST'])]
37    public function generateVapidKeys(Request $request): JsonResponse
38    {
39        $this->userHasPermission(PermissionType::CONFIGURATION_EDIT);
40
41        $data = json_decode($request->getContent());
42        if (!$data instanceof \stdClass) {
43            $data = new \stdClass();
44        }
45
46        if (!Token::getInstance($this->session)->verifyToken('pmf-csrf-token', (string) ($data->csrf ?? ''))) {
47            return $this->json([
48                'success' => false,
49                'error' => Translation::get('msgNoPermission'),
50            ], Response::HTTP_UNAUTHORIZED);
51        }
52
53        try {
54            $keys = WebPushService::generateVapidKeys();
55
56            $this->configuration->update(['push.vapidPublicKey' => $keys['publicKey']]);
57            $this->configuration->update(['push.vapidPrivateKey' => $keys['privateKey']]);
58
59            if (($this->configuration->get('push.vapidSubject') ?? '') === '') {
60                $this->configuration->update([
61                    'push.vapidSubject' => 'mailto:' . $this->configuration->getAdminEmail(),
62                ]);
63            }
64
65            return $this->json([
66                'success' => true,
67                'publicKey' => $keys['publicKey'],
68            ], Response::HTTP_OK);
69        } catch (\Throwable $e) {
70            $this->configuration->getLogger()->error('VAPID key generation failed: ' . $e->getMessage(), [
71                'exception' => $e->getTraceAsString(),
72            ]);
73
74            return $this->json([
75                'success' => false,
76                'error' => 'An internal error occurred',
77            ], Response::HTTP_INTERNAL_SERVER_ERROR);
78        }
79    }
80}