Skip to content

Commit e4caee0

Browse files
committed
feat(api): refactored private API to request new password
1 parent 7c47914 commit e4caee0

File tree

10 files changed

+200
-88
lines changed

10 files changed

+200
-88
lines changed

nginx.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ server {
7676
rewrite user/(ucp|bookmarks|request-removal|logout) /index.php?action=$1 last;
7777

7878
# Private APIs
79-
rewrite api/(autocomplete|bookmark/([0-9]+)) /api/index.php last;
79+
rewrite api/(autocomplete|bookmark/([0-9]+)|user/(.*)) /api/index.php last;
8080

8181
# Setup APIs
8282
rewrite api/setup/(check|backup|update-database) /api/index.php last;

phpmyfaq/.htaccess

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Header set Access-Control-Allow-Headers "Content-Type, Authorization"
9595
RewriteRule user/(ucp|bookmarks|request-removal|logout) index.php?action=$1 [L,QSA]
9696

9797
# Private APIs
98-
RewriteRule api/(autocomplete|bookmark/([0-9]+)|user/data/update) api/index.php [L,QSA]
98+
RewriteRule api/(autocomplete|bookmark/([0-9]+)|user/(.*)) api/index.php [L,QSA]
9999

100100
# Setup APIs
101101
RewriteRule api/setup/(check|backup|update-database) api/index.php [L,QSA]

phpmyfaq/api.service.php

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
const IS_VALID_PHPMYFAQ = null;
2121

22-
use phpMyFAQ\Bookmark;
2322
use phpMyFAQ\Captcha\Captcha;
2423
use phpMyFAQ\Category;
2524
use phpMyFAQ\Comments;
@@ -844,68 +843,6 @@
844843

845844
break;
846845

847-
//
848-
// Change password
849-
//
850-
case 'change-password':
851-
$postData = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR);
852-
$username = trim((string) Filter::filterVar($postData['username'], FILTER_SANITIZE_SPECIAL_CHARS));
853-
$email = trim((string) Filter::filterVar($postData['email'], FILTER_VALIDATE_EMAIL));
854-
855-
if ($username !== '' && $username !== '0' && ($email !== '' && $email !== '0')) {
856-
$user = new CurrentUser($faqConfig);
857-
$loginExist = $user->getUserByLogin($username);
858-
859-
if ($loginExist && ($email == $user->getUserData('email'))) {
860-
try {
861-
$newPassword = $user->createPassword();
862-
} catch (Exception $exception) {
863-
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
864-
$response->setData(['error' => $exception->getMessage()]);
865-
}
866-
867-
try {
868-
$user->changePassword($newPassword);
869-
} catch (Exception $exception) {
870-
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
871-
$response->setData(['error' => $exception->getMessage()]);
872-
}
873-
874-
$text = Translation::get('lostpwd_text_1') . "\nUsername: " . $username . "\nNew Password: " .
875-
$newPassword . "\n\n" . Translation::get('lostpwd_text_2');
876-
877-
$mailer = new Mail($faqConfig);
878-
try {
879-
$mailer->addTo($email);
880-
} catch (Exception $exception) {
881-
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
882-
$response->setData(['error' => $exception->getMessage()]);
883-
}
884-
885-
$mailer->subject = Utils::resolveMarkers('[%sitename%] Username / password request', $faqConfig);
886-
$mailer->message = $text;
887-
try {
888-
$result = $mailer->send();
889-
} catch (Exception | TransportExceptionInterface $exception) {
890-
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
891-
$response->setData(['error' => $exception->getMessage()]);
892-
}
893-
894-
unset($mailer);
895-
// Trust that the email has been sent
896-
$response->setStatusCode(Response::HTTP_OK);
897-
$response->setData(['success' => Translation::get('lostpwd_mail_okay')]);
898-
} else {
899-
$response->setStatusCode(Response::HTTP_CONFLICT);
900-
$response->setData(['error' => Translation::get('lostpwd_err_1')]);
901-
}
902-
} else {
903-
$response->setStatusCode(Response::HTTP_CONFLICT);
904-
$response->setData(['error' => Translation::get('lostpwd_err_2')]);
905-
}
906-
907-
break;
908-
909846
//
910847
// Request removal of user
911848
//

phpmyfaq/assets/src/api/user.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,26 @@ export const updateUserControlPanelData = async (data) => {
3737
console.error(error);
3838
}
3939
};
40+
41+
export const updateUserPassword = async (data) => {
42+
try {
43+
const response = await fetch('api/user/password/update', {
44+
method: 'PUT',
45+
cache: 'no-cache',
46+
headers: {
47+
'Content-Type': 'application/json',
48+
},
49+
body: JSON.stringify(serialize(data)),
50+
redirect: 'follow',
51+
referrerPolicy: 'no-referrer',
52+
});
53+
54+
if (response.ok) {
55+
return await response.json();
56+
} else {
57+
return await response.json();
58+
}
59+
} catch (error) {
60+
console.error(error);
61+
}
62+
};

phpmyfaq/assets/src/frontend.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import Masonry from 'masonry-layout';
1919
import { handleBookmarks, saveFormData } from './api';
2020
import { handleComments, handleSaveComment, handleUserVoting } from './faq';
2121
import { handleAutoComplete, handleQuestion } from './search';
22+
import { handleUserControlPanel, handleUserPassword } from './user';
2223
import { calculateReadingTime, handlePasswordStrength, handlePasswordToggle, handleReloadCaptcha } from './utils';
2324
import './utils/tooltip';
24-
import { handleUserControlPanel } from './user';
2525

2626
//
2727
// Reload Captchas
@@ -66,6 +66,11 @@ handleBookmarks();
6666
//
6767
handleUserControlPanel();
6868

69+
//
70+
// Handle user password
71+
//
72+
handleUserPassword();
73+
6974
//
7075
// Masonry on startpage
7176
//

phpmyfaq/assets/src/user/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './password';
12
export * from './ucp';
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* User request password functionality
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public License,
5+
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
6+
* obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* @package phpMyFAQ
9+
* @author Thorsten Rinne <[email protected]>
10+
* @copyright 2024 phpMyFAQ Team
11+
* @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
12+
* @link https://www.phpmyfaq.de
13+
* @since 2024-03-03
14+
*/
15+
16+
import { updateUserPassword } from '../api/user';
17+
import { addElement } from '../utils';
18+
19+
export const handleUserPassword = () => {
20+
const changePasswordSubmit = document.getElementById('pmf-submit-password');
21+
22+
if (changePasswordSubmit) {
23+
changePasswordSubmit.addEventListener('click', async (event) => {
24+
event.preventDefault();
25+
26+
const form = document.querySelector('#pmf-password-form');
27+
const loader = document.getElementById('loader');
28+
const formData = new FormData(form);
29+
30+
const response = await updateUserPassword(formData);
31+
32+
if (response.success) {
33+
loader.classList.add('d-none');
34+
const message = document.getElementById('pmf-password-response');
35+
message.insertAdjacentElement(
36+
'afterend',
37+
addElement('div', { classList: 'alert alert-success', innerText: response.success })
38+
);
39+
}
40+
41+
if (response.error) {
42+
loader.classList.add('d-none');
43+
const message = document.getElementById('pmf-password-response');
44+
message.insertAdjacentElement(
45+
'afterend',
46+
addElement('div', { classList: 'alert alert-danger', innerText: response.error })
47+
);
48+
}
49+
});
50+
}
51+
};
Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,50 @@
11
<section class="col-12">
2-
<h1>{{ pageHeader }}</h1>
32

43
<div class="row mb-2">
54
<div class="col">
65
<div class="spinner-border text-primary d-none" id="loader" role="status">
76
<span class="visually-hidden">Loading...</span>
87
</div>
9-
<div id="faqs"></div>
8+
<div id="pmf-password-response"></div>
109
</div>
1110
</div>
1211

13-
<form id="formValues" action="#" method="post" accept-charset="utf-8" class="needs-validation" novalidate>
14-
<input type="hidden" name="lang" id="lang" value="{{ lang }}" />
12+
<div class="row">
13+
<div class="col-6 offset-3">
14+
<div class="card shadow">
15+
<div class="card-header">
16+
<h4 class="card-title">{{ pageHeader }}</h4>
17+
</div>
18+
<div class="card-body">
19+
<form id="pmf-password-form" action="#" method="post" accept-charset="utf-8" class="needs-validation"
20+
novalidate>
21+
<input type="hidden" name="lang" id="lang" value="{{ lang }}">
1522

16-
<div class="row mb-2">
17-
<label class="col-3 form-control-label" for="username">{{ msgUsername }}</label>
18-
<div class="col-9">
19-
<input class="form-control" type="text" name="username" id="username" required autofocus />
20-
</div>
21-
</div>
23+
<div class="row mb-2">
24+
<label class="col-3 form-control-label" for="username">{{ msgUsername }}</label>
25+
<div class="col-9">
26+
<input class="form-control" type="text" name="username" id="username" required autofocus>
27+
</div>
28+
</div>
2229

23-
<div class="row mb-2">
24-
<label class="col-3 form-control-label" for="email">{{ msgEmail }}</label>
25-
<div class="col-9">
26-
<input class="form-control" type="email" name="email" id="email" required />
27-
</div>
28-
</div>
30+
<div class="row mb-2">
31+
<label class="col-3 form-control-label" for="email">{{ msgEmail }}</label>
32+
<div class="col-9">
33+
<input class="form-control" type="email" name="email" id="email" required>
34+
</div>
35+
</div>
2936

30-
<div class="row mb-2">
31-
<div class="col-sm-12 text-end">
32-
<button class="btn btn-primary" type="submit" id="pmf-submit-values" data-pmf-form="change-password">
33-
{{ msgSubmit }}
34-
</button>
37+
<div class="row mb-2">
38+
<div class="col-sm-12 text-end">
39+
<button class="btn btn-primary" type="submit" id="pmf-submit-password" data-pmf-form="change-password">
40+
{{ msgSubmit }}
41+
</button>
42+
</div>
43+
</div>
44+
</form>
45+
</div>
3546
</div>
3647
</div>
37-
</form>
48+
</div>
49+
3850
</section>

phpmyfaq/src/api-routes.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@
164164
'api.bookmark',
165165
new Route('bookmark/{bookmarkId}', ['_controller' => [BookmarkController::class, 'delete'], '_methods' => 'DELETE'])
166166
);
167+
$routes->add(
168+
'api.user.password',
169+
new Route('user/password/update', ['_controller' => [UserController::class, 'updatePassword'], '_methods' => 'PUT'])
170+
);
167171
$routes->add(
168172
'api.user.update',
169173
new Route('user/data/update', ['_controller' => [UserController::class, 'updateData'], '_methods' => 'PUT'])

phpmyfaq/src/phpMyFAQ/Controller/Frontend/UserController.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121
use phpMyFAQ\Controller\AbstractController;
2222
use phpMyFAQ\Core\Exception;
2323
use phpMyFAQ\Filter;
24+
use phpMyFAQ\Mail;
2425
use phpMyFAQ\Session\Token;
2526
use phpMyFAQ\Translation;
2627
use phpMyFAQ\User\CurrentUser;
28+
use phpMyFAQ\Utils;
2729
use Symfony\Component\HttpFoundation\JsonResponse;
2830
use Symfony\Component\HttpFoundation\Request;
2931
use Symfony\Component\HttpFoundation\Response;
32+
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
3033
use Symfony\Component\Routing\Attribute\Route;
3134

3235
class UserController extends AbstractController
@@ -128,4 +131,80 @@ public function updateData(Request $request): JsonResponse
128131

129132
return $jsonResponse;
130133
}
134+
135+
/**
136+
* @throws Exception
137+
*/
138+
#[Route('api/user/data/update', methods: ['PUT'])]
139+
public function updatePassword(Request $request): JsonResponse
140+
{
141+
$jsonResponse = new JsonResponse();
142+
$configuration = Configuration::getConfigurationInstance();
143+
$user = CurrentUser::getCurrentUser($configuration);
144+
145+
$data = json_decode($request->getContent());
146+
147+
$username = trim((string) Filter::filterVar($data->username, FILTER_SANITIZE_SPECIAL_CHARS));
148+
$email = trim((string) Filter::filterVar($data->email, FILTER_VALIDATE_EMAIL));
149+
if ($username !== '' && $username !== '0' && ($email !== '' && $email !== '0')) {
150+
$loginExist = $user->getUserByLogin($username);
151+
152+
if ($loginExist && ($email == $user->getUserData('email'))) {
153+
try {
154+
$newPassword = $user->createPassword();
155+
} catch (Exception $exception) {
156+
$jsonResponse->setStatusCode(Response::HTTP_BAD_REQUEST);
157+
$jsonResponse->setData(['error' => $exception->getMessage()]);
158+
return $jsonResponse;
159+
}
160+
161+
try {
162+
$user->changePassword($newPassword);
163+
} catch (\Exception $exception) {
164+
$jsonResponse->setStatusCode(Response::HTTP_BAD_REQUEST);
165+
$jsonResponse->setData(['error' => $exception->getMessage()]);
166+
return $jsonResponse;
167+
}
168+
169+
$text = Translation::get('lostpwd_text_1') .
170+
sprintf('<br><br>Username: %s', $username) .
171+
sprintf('<br>New Password: %s<br><br>', $newPassword) .
172+
Translation::get('lostpwd_text_2');
173+
174+
$mailer = new Mail($configuration);
175+
try {
176+
$mailer->addTo($email);
177+
} catch (Exception $exception) {
178+
$jsonResponse->setStatusCode(Response::HTTP_BAD_REQUEST);
179+
$jsonResponse->setData(['error' => $exception->getMessage()]);
180+
return $jsonResponse;
181+
}
182+
183+
$mailer->subject = Utils::resolveMarkers('[%sitename%] Username / password request', $configuration);
184+
$mailer->message = $text;
185+
try {
186+
$result = $mailer->send();
187+
} catch (Exception | TransportExceptionInterface $exception) {
188+
$jsonResponse->setStatusCode(Response::HTTP_BAD_REQUEST);
189+
$jsonResponse->setData(['error' => $exception->getMessage()]);
190+
return $jsonResponse;
191+
}
192+
193+
unset($mailer);
194+
// Trust that the email has been sent
195+
$jsonResponse->setStatusCode(Response::HTTP_OK);
196+
$jsonResponse->setData(['success' => Translation::get('lostpwd_mail_okay')]);
197+
} else {
198+
$jsonResponse->setStatusCode(Response::HTTP_CONFLICT);
199+
$jsonResponse->setData(['error' => Translation::get('lostpwd_err_1')]);
200+
return $jsonResponse;
201+
}
202+
} else {
203+
$jsonResponse->setStatusCode(Response::HTTP_CONFLICT);
204+
$jsonResponse->setData(['error' => Translation::get('lostpwd_err_2')]);
205+
return $jsonResponse;
206+
}
207+
208+
return $jsonResponse;
209+
}
131210
}

0 commit comments

Comments
 (0)