1 | <?php |
||||||
2 | /** |
||||||
3 | * ProfileController.php |
||||||
4 | * Copyright (c) 2019 [email protected] |
||||||
5 | * |
||||||
6 | * This file is part of Firefly III (https://github.com/firefly-iii). |
||||||
7 | * |
||||||
8 | * This program is free software: you can redistribute it and/or modify |
||||||
9 | * it under the terms of the GNU Affero General Public License as |
||||||
10 | * published by the Free Software Foundation, either version 3 of the |
||||||
11 | * License, or (at your option) any later version. |
||||||
12 | * |
||||||
13 | * This program is distributed in the hope that it will be useful, |
||||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
16 | * GNU Affero General Public License for more details. |
||||||
17 | * |
||||||
18 | * You should have received a copy of the GNU Affero General Public License |
||||||
19 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
||||||
20 | */ |
||||||
21 | declare(strict_types=1); |
||||||
22 | |||||||
23 | namespace FireflyIII\Http\Controllers; |
||||||
24 | |||||||
25 | use Auth; |
||||||
26 | use DB; |
||||||
27 | use FireflyIII\Events\UserChangedEmail; |
||||||
28 | use FireflyIII\Exceptions\FireflyException; |
||||||
29 | use FireflyIII\Exceptions\ValidationException; |
||||||
30 | use FireflyIII\Http\Middleware\IsDemoUser; |
||||||
31 | use FireflyIII\Http\Middleware\IsSandStormUser; |
||||||
32 | use FireflyIII\Http\Requests\DeleteAccountFormRequest; |
||||||
33 | use FireflyIII\Http\Requests\EmailFormRequest; |
||||||
34 | use FireflyIII\Http\Requests\ProfileFormRequest; |
||||||
35 | use FireflyIII\Http\Requests\TokenFormRequest; |
||||||
36 | use FireflyIII\Models\Preference; |
||||||
37 | use FireflyIII\Repositories\User\UserRepositoryInterface; |
||||||
38 | use FireflyIII\Support\Http\Controllers\CreateStuff; |
||||||
39 | use FireflyIII\Support\Http\Controllers\RequestInformation; |
||||||
40 | use FireflyIII\User; |
||||||
41 | use Google2FA; |
||||||
42 | use Hash; |
||||||
43 | use Illuminate\Contracts\Auth\Guard; |
||||||
44 | use Illuminate\Contracts\View\Factory; |
||||||
45 | use Illuminate\Http\RedirectResponse; |
||||||
46 | use Illuminate\Http\Request; |
||||||
47 | use Illuminate\Routing\Redirector; |
||||||
48 | use Illuminate\Support\Collection; |
||||||
49 | use Illuminate\View\View; |
||||||
50 | use Laravel\Passport\ClientRepository; |
||||||
51 | use Log; |
||||||
52 | use PragmaRX\Recovery\Recovery; |
||||||
53 | |||||||
54 | /** |
||||||
55 | * Class ProfileController. |
||||||
56 | * |
||||||
57 | * @method Guard guard() |
||||||
58 | * |
||||||
59 | */ |
||||||
60 | class ProfileController extends Controller |
||||||
61 | { |
||||||
62 | use RequestInformation, CreateStuff; |
||||||
63 | |||||||
64 | /** |
||||||
65 | * ProfileController constructor. |
||||||
66 | * |
||||||
67 | * @codeCoverageIgnore |
||||||
68 | */ |
||||||
69 | public function __construct() |
||||||
70 | { |
||||||
71 | parent::__construct(); |
||||||
72 | |||||||
73 | $this->middleware( |
||||||
74 | static function ($request, $next) { |
||||||
75 | app('view')->share('title', (string) trans('firefly.profile')); |
||||||
76 | app('view')->share('mainTitleIcon', 'fa-user'); |
||||||
77 | |||||||
78 | return $next($request); |
||||||
79 | } |
||||||
80 | ); |
||||||
81 | |||||||
82 | $this->middleware(IsDemoUser::class)->except(['index']); |
||||||
83 | $this->middleware(IsSandStormUser::class)->except('index'); |
||||||
84 | } |
||||||
85 | |||||||
86 | /** |
||||||
87 | * Change your email address. |
||||||
88 | * |
||||||
89 | * @param Request $request |
||||||
90 | * |
||||||
91 | * @return Factory|RedirectResponse|Redirector|View |
||||||
92 | */ |
||||||
93 | public function changeEmail(Request $request) |
||||||
94 | { |
||||||
95 | $loginProvider = config('firefly.login_provider'); |
||||||
96 | if ('eloquent' !== $loginProvider) { |
||||||
97 | // @codeCoverageIgnoreStart |
||||||
98 | $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); |
||||||
99 | |||||||
100 | return redirect(route('profile.index')); |
||||||
101 | // @codeCoverageIgnoreEnd |
||||||
102 | } |
||||||
103 | |||||||
104 | $title = auth()->user()->email; |
||||||
105 | $email = auth()->user()->email; |
||||||
106 | $subTitle = (string) trans('firefly.change_your_email'); |
||||||
107 | $subTitleIcon = 'fa-envelope'; |
||||||
108 | |||||||
109 | return view('profile.change-email', compact('title', 'subTitle', 'subTitleIcon', 'email')); |
||||||
110 | } |
||||||
111 | |||||||
112 | /** |
||||||
113 | * Change your password. |
||||||
114 | * |
||||||
115 | * @param Request $request |
||||||
116 | * |
||||||
117 | * @return Factory|RedirectResponse|Redirector|View |
||||||
118 | */ |
||||||
119 | public function changePassword(Request $request) |
||||||
120 | { |
||||||
121 | $loginProvider = config('firefly.login_provider'); |
||||||
122 | if ('eloquent' !== $loginProvider) { |
||||||
123 | // @codeCoverageIgnoreStart |
||||||
124 | $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); |
||||||
125 | |||||||
126 | return redirect(route('profile.index')); |
||||||
127 | // @codeCoverageIgnoreEnd |
||||||
128 | } |
||||||
129 | |||||||
130 | $title = auth()->user()->email; |
||||||
131 | $subTitle = (string) trans('firefly.change_your_password'); |
||||||
132 | $subTitleIcon = 'fa-key'; |
||||||
133 | |||||||
134 | return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon')); |
||||||
135 | } |
||||||
136 | |||||||
137 | /** |
||||||
138 | * View that generates a 2FA code for the user. |
||||||
139 | * |
||||||
140 | * @return Factory|View |
||||||
141 | */ |
||||||
142 | public function code() |
||||||
143 | { |
||||||
144 | $domain = $this->getDomain(); |
||||||
145 | $secret = null; |
||||||
146 | |||||||
147 | // generate secret if not in session |
||||||
148 | if (!session()->has('temp-mfa-secret')) { |
||||||
149 | // generate secret + store + flash |
||||||
150 | $secret = Google2FA::generateSecretKey(); |
||||||
151 | session()->put('temp-mfa-secret', $secret); |
||||||
152 | session()->flash('two-factor-secret', $secret); |
||||||
153 | } |
||||||
154 | // re-use secret if in session |
||||||
155 | if (session()->has('temp-mfa-secret')) { |
||||||
156 | // get secret from session and flash |
||||||
157 | $secret = session()->get('temp-mfa-secret'); |
||||||
158 | session()->flash('two-factor-secret', $secret); |
||||||
159 | } |
||||||
160 | |||||||
161 | // generate codes if not in session: |
||||||
162 | if (!session()->has('temp-mfa-codes')) { |
||||||
163 | // generate codes + store + flash: |
||||||
164 | $recovery = app(Recovery::class); |
||||||
165 | $recoveryCodes = $recovery->lowercase()->setCount(8)->setBlocks(2)->setChars(6)->toArray(); |
||||||
166 | session()->put('temp-mfa-codes', $recoveryCodes); |
||||||
167 | session()->flash('two-factor-codes', $recoveryCodes); |
||||||
168 | } |
||||||
169 | |||||||
170 | // get codes from session if there already: |
||||||
171 | if (session()->has('temp-mfa-codes')) { |
||||||
172 | $recoveryCodes = session()->get('temp-mfa-codes'); |
||||||
173 | session()->flash('two-factor-codes', $recoveryCodes); |
||||||
174 | } |
||||||
175 | |||||||
176 | $codes = implode("\r\n", $recoveryCodes); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||||||
177 | |||||||
178 | $image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret); |
||||||
179 | |||||||
180 | return view('profile.code', compact('image', 'secret', 'codes')); |
||||||
181 | } |
||||||
182 | |||||||
183 | /** |
||||||
184 | * Screen to confirm email change. |
||||||
185 | * |
||||||
186 | * @param UserRepositoryInterface $repository |
||||||
187 | * @param string $token |
||||||
188 | * |
||||||
189 | * @throws FireflyException |
||||||
190 | * @return RedirectResponse|Redirector |
||||||
191 | * |
||||||
192 | */ |
||||||
193 | public function confirmEmailChange(UserRepositoryInterface $repository, string $token) |
||||||
194 | { |
||||||
195 | $loginProvider = config('firefly.login_provider'); |
||||||
196 | if ('eloquent' !== $loginProvider) { |
||||||
197 | // @codeCoverageIgnoreStart |
||||||
198 | throw new FireflyException('Cannot confirm email change when authentication provider is not local.'); |
||||||
199 | // @codeCoverageIgnoreEnd |
||||||
200 | } |
||||||
201 | // find preference with this token value. |
||||||
202 | /** @var Collection $set */ |
||||||
203 | $set = app('preferences')->findByName('email_change_confirm_token'); |
||||||
204 | $user = null; |
||||||
205 | //Log::debug(sprintf('Found %d preferences', $set->count())); |
||||||
206 | /** @var Preference $preference */ |
||||||
207 | foreach ($set as $preference) { |
||||||
208 | if ($preference->data === $token) { |
||||||
209 | //Log::debug('Found user'); |
||||||
210 | $user = $preference->user; |
||||||
211 | } |
||||||
212 | } |
||||||
213 | // update user to clear blocked and blocked_code. |
||||||
214 | if (null === $user) { |
||||||
215 | //Log::debug('Found no user'); |
||||||
216 | throw new FireflyException('Invalid token.'); |
||||||
217 | } |
||||||
218 | //Log::debug('Will unblock user.'); |
||||||
219 | $repository->unblockUser($user); |
||||||
220 | |||||||
221 | // return to login. |
||||||
222 | session()->flash('success', (string) trans('firefly.login_with_new_email')); |
||||||
223 | |||||||
224 | return redirect(route('login')); |
||||||
225 | } |
||||||
226 | |||||||
227 | /** |
||||||
228 | * Delete your account view. |
||||||
229 | * |
||||||
230 | * @param Request $request |
||||||
231 | * |
||||||
232 | * @return Factory|View |
||||||
233 | */ |
||||||
234 | public function deleteAccount(Request $request) |
||||||
235 | { |
||||||
236 | $loginProvider = config('firefly.login_provider'); |
||||||
237 | if ('eloquent' !== $loginProvider) { |
||||||
238 | // @codeCoverageIgnoreStart |
||||||
239 | $request->session()->flash('warning', trans('firefly.delete_local_info_only', ['login_provider' => e($loginProvider)])); |
||||||
240 | // @codeCoverageIgnoreEnd |
||||||
241 | } |
||||||
242 | $title = auth()->user()->email; |
||||||
243 | $subTitle = (string) trans('firefly.delete_account'); |
||||||
244 | $subTitleIcon = 'fa-trash'; |
||||||
245 | |||||||
246 | return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon')); |
||||||
247 | } |
||||||
248 | |||||||
249 | /** |
||||||
250 | * Delete 2FA routine. |
||||||
251 | * |
||||||
252 | * @return RedirectResponse|Redirector |
||||||
253 | */ |
||||||
254 | public function deleteCode() |
||||||
255 | { |
||||||
256 | /** @var UserRepositoryInterface $repository */ |
||||||
257 | $repository = app(UserRepositoryInterface::class); |
||||||
258 | |||||||
259 | /** @var User $user */ |
||||||
260 | $user = auth()->user(); |
||||||
261 | |||||||
262 | $repository->setMFACode($user, null); |
||||||
263 | session()->flash('success', (string) trans('firefly.pref_two_factor_auth_disabled')); |
||||||
264 | session()->flash('info', (string) trans('firefly.pref_two_factor_auth_remove_it')); |
||||||
265 | |||||||
266 | return redirect(route('profile.index')); |
||||||
267 | } |
||||||
268 | |||||||
269 | /** |
||||||
270 | * Enable 2FA screen. |
||||||
271 | * |
||||||
272 | * @return RedirectResponse|Redirector |
||||||
273 | */ |
||||||
274 | public function enable2FA() |
||||||
275 | { |
||||||
276 | /** @var User $user */ |
||||||
277 | $user = auth()->user(); |
||||||
278 | $enabledMFA = null !== $user->mfa_secret; |
||||||
279 | |||||||
280 | // if we don't have a valid secret yet, redirect to the code page to get one. |
||||||
281 | if (!$enabledMFA) { |
||||||
282 | return redirect(route('profile.code')); |
||||||
283 | } |
||||||
284 | |||||||
285 | // If FF3 already has a secret, just set the two factor auth enabled to 1, |
||||||
286 | // and let the user continue with the existing secret. |
||||||
287 | session()->flash('info', (string) trans('firefly.2fa_already_enabled')); |
||||||
288 | |||||||
289 | return redirect(route('profile.index')); |
||||||
290 | } |
||||||
291 | |||||||
292 | /** |
||||||
293 | * Index for profile. |
||||||
294 | * |
||||||
295 | * @return Factory|View |
||||||
296 | */ |
||||||
297 | public function index() |
||||||
298 | { |
||||||
299 | /** @var User $user */ |
||||||
300 | $user = auth()->user(); |
||||||
301 | $loginProvider = config('firefly.login_provider'); |
||||||
302 | // check if client token thing exists (default one) |
||||||
303 | $count = DB::table('oauth_clients')->where('personal_access_client', 1)->whereNull('user_id')->count(); |
||||||
304 | |||||||
305 | $this->createOAuthKeys(); |
||||||
306 | |||||||
307 | if (0 === $count) { |
||||||
308 | /** @var ClientRepository $repository */ |
||||||
309 | $repository = app(ClientRepository::class); |
||||||
310 | $repository->createPersonalAccessClient(null, config('app.name') . ' Personal Access Client', 'http://localhost'); |
||||||
311 | } |
||||||
312 | $subTitle = $user->email; |
||||||
313 | $userId = $user->id; |
||||||
314 | $enabled2FA = null !== $user->mfa_secret; |
||||||
315 | $mfaBackupCount = count(app('preferences')->get('mfa_recovery', [])->data); |
||||||
0 ignored issues
–
show
app('preferences')->get(...covery', array())->data of type string is incompatible with the type Countable|array expected by parameter $var of count() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
316 | |||||||
317 | // get access token or create one. |
||||||
318 | $accessToken = app('preferences')->get('access_token', null); |
||||||
319 | if (null === $accessToken) { |
||||||
320 | $token = $user->generateAccessToken(); |
||||||
321 | $accessToken = app('preferences')->set('access_token', $token); |
||||||
322 | } |
||||||
323 | |||||||
324 | return view('profile.index', compact('subTitle', 'mfaBackupCount', 'userId', 'accessToken', 'enabled2FA', 'loginProvider')); |
||||||
325 | } |
||||||
326 | |||||||
327 | /** |
||||||
328 | * @return Factory|View |
||||||
329 | */ |
||||||
330 | public function newBackupCodes() |
||||||
331 | { |
||||||
332 | // generate recovery codes: |
||||||
333 | $recovery = app(Recovery::class); |
||||||
334 | $recoveryCodes = $recovery->lowercase() |
||||||
335 | ->setCount(8) // Generate 8 codes |
||||||
336 | ->setBlocks(2) // Every code must have 7 blocks |
||||||
337 | ->setChars(6) // Each block must have 16 chars |
||||||
338 | ->toArray(); |
||||||
339 | $codes = implode("\r\n", $recoveryCodes); |
||||||
340 | |||||||
341 | app('preferences')->set('mfa_recovery', $recoveryCodes); |
||||||
342 | app('preferences')->mark(); |
||||||
343 | |||||||
344 | return view('profile.new-backup-codes', compact('codes')); |
||||||
345 | } |
||||||
346 | |||||||
347 | /** |
||||||
348 | * Submit the change email form. |
||||||
349 | * |
||||||
350 | * @param EmailFormRequest $request |
||||||
351 | * @param UserRepositoryInterface $repository |
||||||
352 | * |
||||||
353 | * @return $this|RedirectResponse|Redirector |
||||||
354 | */ |
||||||
355 | public function postChangeEmail(EmailFormRequest $request, UserRepositoryInterface $repository) |
||||||
356 | { |
||||||
357 | $loginProvider = config('firefly.login_provider'); |
||||||
358 | if ('eloquent' !== $loginProvider) { |
||||||
359 | // @codeCoverageIgnoreStart |
||||||
360 | $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); |
||||||
361 | |||||||
362 | return redirect(route('profile.index')); |
||||||
363 | // @codeCoverageIgnoreEnd |
||||||
364 | } |
||||||
365 | |||||||
366 | /** @var User $user */ |
||||||
367 | $user = auth()->user(); |
||||||
368 | $newEmail = $request->string('email'); |
||||||
369 | $oldEmail = $user->email; |
||||||
370 | if ($newEmail === $user->email) { |
||||||
371 | session()->flash('error', (string) trans('firefly.email_not_changed')); |
||||||
372 | |||||||
373 | return redirect(route('profile.change-email'))->withInput(); |
||||||
374 | } |
||||||
375 | $existing = $repository->findByEmail($newEmail); |
||||||
376 | if (null !== $existing) { |
||||||
377 | // force user logout. |
||||||
378 | Auth::guard()->logout(); |
||||||
379 | $request->session()->invalidate(); |
||||||
380 | |||||||
381 | session()->flash('success', (string) trans('firefly.email_changed')); |
||||||
382 | |||||||
383 | return redirect(route('index')); |
||||||
384 | } |
||||||
385 | |||||||
386 | // now actually update user: |
||||||
387 | $repository->changeEmail($user, $newEmail); |
||||||
388 | |||||||
389 | // call event. |
||||||
390 | $ipAddress = $request->ip(); |
||||||
391 | event(new UserChangedEmail($user, $newEmail, $oldEmail, $ipAddress)); |
||||||
0 ignored issues
–
show
It seems like
$ipAddress can also be of type null ; however, parameter $ipAddress of FireflyIII\Events\UserChangedEmail::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
392 | |||||||
393 | // force user logout. |
||||||
394 | Auth::guard()->logout(); |
||||||
395 | $request->session()->invalidate(); |
||||||
396 | session()->flash('success', (string) trans('firefly.email_changed')); |
||||||
397 | |||||||
398 | return redirect(route('index')); |
||||||
399 | } |
||||||
400 | |||||||
401 | /** |
||||||
402 | * Submit change password form. |
||||||
403 | * |
||||||
404 | * @param ProfileFormRequest $request |
||||||
405 | * @param UserRepositoryInterface $repository |
||||||
406 | * |
||||||
407 | * @return RedirectResponse|Redirector |
||||||
408 | */ |
||||||
409 | public function postChangePassword(ProfileFormRequest $request, UserRepositoryInterface $repository) |
||||||
410 | { |
||||||
411 | $loginProvider = config('firefly.login_provider'); |
||||||
412 | if ('eloquent' !== $loginProvider) { |
||||||
413 | // @codeCoverageIgnoreStart |
||||||
414 | $request->session()->flash('error', trans('firefly.login_provider_local_only', ['login_provider' => e($loginProvider)])); |
||||||
415 | |||||||
416 | return redirect(route('profile.index')); |
||||||
417 | // @codeCoverageIgnoreEnd |
||||||
418 | } |
||||||
419 | |||||||
420 | // the request has already validated both new passwords must be equal. |
||||||
421 | $current = $request->get('current_password'); |
||||||
422 | $new = $request->get('new_password'); |
||||||
423 | /** @var User $user */ |
||||||
424 | $user = auth()->user(); |
||||||
425 | try { |
||||||
426 | $this->validatePassword($user, $current, $new); |
||||||
0 ignored issues
–
show
It seems like
$new can also be of type null ; however, parameter $new of FireflyIII\Http\Controll...ler::validatePassword() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like
$current can also be of type null ; however, parameter $current of FireflyIII\Http\Controll...ler::validatePassword() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
427 | } catch (ValidationException $e) { |
||||||
428 | session()->flash('error', $e->getMessage()); |
||||||
429 | |||||||
430 | return redirect(route('profile.change-password')); |
||||||
431 | } |
||||||
432 | |||||||
433 | $repository->changePassword($user, $request->get('new_password')); |
||||||
0 ignored issues
–
show
It seems like
$request->get('new_password') can also be of type null ; however, parameter $password of FireflyIII\Repositories\...rface::changePassword() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
434 | session()->flash('success', (string) trans('firefly.password_changed')); |
||||||
435 | |||||||
436 | return redirect(route('profile.index')); |
||||||
437 | } |
||||||
438 | |||||||
439 | /** @noinspection PhpUnusedParameterInspection */ |
||||||
440 | /** |
||||||
441 | * Submit 2FA for the first time. |
||||||
442 | * |
||||||
443 | * @param TokenFormRequest $request |
||||||
444 | * |
||||||
445 | * @return RedirectResponse|Redirector |
||||||
446 | */ |
||||||
447 | public function postCode(TokenFormRequest $request) |
||||||
448 | { |
||||||
449 | /** @var User $user */ |
||||||
450 | $user = auth()->user(); |
||||||
451 | /** @var UserRepositoryInterface $repository */ |
||||||
452 | $repository = app(UserRepositoryInterface::class); |
||||||
453 | /** @var string $secret */ |
||||||
454 | $secret = session()->get('two-factor-secret'); |
||||||
455 | $repository->setMFACode($user, $secret); |
||||||
456 | |||||||
457 | session()->flash('success', (string) trans('firefly.saved_preferences')); |
||||||
458 | app('preferences')->mark(); |
||||||
459 | |||||||
460 | // also save the code so replay attack is prevented. |
||||||
461 | $mfaCode = $request->get('code'); |
||||||
462 | $this->addToMFAHistory($mfaCode); |
||||||
0 ignored issues
–
show
It seems like
$mfaCode can also be of type null ; however, parameter $mfaCode of FireflyIII\Http\Controll...ller::addToMFAHistory() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
463 | |||||||
464 | // save backup codes in preferences: |
||||||
465 | app('preferences')->set('mfa_recovery', session()->get('temp-mfa-codes')); |
||||||
466 | |||||||
467 | // make sure MFA is logged out. |
||||||
468 | if ('testing' !== config('app.env')) { |
||||||
469 | Google2FA::logout(); |
||||||
470 | } |
||||||
471 | |||||||
472 | // drop all info from session: |
||||||
473 | session()->forget(['temp-mfa-secret', 'two-factor-secret', 'temp-mfa-codes', 'two-factor-codes']); |
||||||
474 | |||||||
475 | return redirect(route('profile.index')); |
||||||
476 | } |
||||||
477 | |||||||
478 | /** |
||||||
479 | * Submit delete account. |
||||||
480 | * |
||||||
481 | * @param UserRepositoryInterface $repository |
||||||
482 | * @param DeleteAccountFormRequest $request |
||||||
483 | * |
||||||
484 | * @return RedirectResponse|Redirector |
||||||
485 | */ |
||||||
486 | public function postDeleteAccount(UserRepositoryInterface $repository, DeleteAccountFormRequest $request) |
||||||
487 | { |
||||||
488 | if (!Hash::check($request->get('password'), auth()->user()->password)) { |
||||||
489 | session()->flash('error', (string) trans('firefly.invalid_password')); |
||||||
490 | |||||||
491 | return redirect(route('profile.delete-account')); |
||||||
492 | } |
||||||
493 | /** @var User $user */ |
||||||
494 | $user = auth()->user(); |
||||||
495 | Log::info(sprintf('User #%d has opted to delete their account', auth()->user()->id)); |
||||||
496 | // make repository delete user: |
||||||
497 | auth()->logout(); |
||||||
498 | session()->flush(); |
||||||
499 | $repository->destroy($user); |
||||||
500 | |||||||
501 | return redirect(route('index')); |
||||||
502 | } |
||||||
503 | |||||||
504 | /** |
||||||
505 | * Regenerate access token. |
||||||
506 | * |
||||||
507 | * @return RedirectResponse|Redirector |
||||||
508 | */ |
||||||
509 | public function regenerate() |
||||||
510 | { |
||||||
511 | /** @var User $user */ |
||||||
512 | $user = auth()->user(); |
||||||
513 | $token = $user->generateAccessToken(); |
||||||
514 | app('preferences')->set('access_token', $token); |
||||||
515 | session()->flash('success', (string) trans('firefly.token_regenerated')); |
||||||
516 | |||||||
517 | return redirect(route('profile.index')); |
||||||
518 | } |
||||||
519 | |||||||
520 | /** |
||||||
521 | * Undo change of user email address. |
||||||
522 | * |
||||||
523 | * @param UserRepositoryInterface $repository |
||||||
524 | * @param string $token |
||||||
525 | * @param string $hash |
||||||
526 | * |
||||||
527 | * @throws FireflyException |
||||||
528 | * @return RedirectResponse|Redirector |
||||||
529 | * |
||||||
530 | */ |
||||||
531 | public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash) |
||||||
532 | { |
||||||
533 | $loginProvider = config('firefly.login_provider'); |
||||||
534 | if ('eloquent' !== $loginProvider) { |
||||||
535 | // @codeCoverageIgnoreStart |
||||||
536 | throw new FireflyException('Cannot confirm email change when authentication provider is not local.'); |
||||||
537 | // @codeCoverageIgnoreEnd |
||||||
538 | } |
||||||
539 | |||||||
540 | // find preference with this token value. |
||||||
541 | $set = app('preferences')->findByName('email_change_undo_token'); |
||||||
542 | $user = null; |
||||||
543 | /** @var Preference $preference */ |
||||||
544 | foreach ($set as $preference) { |
||||||
545 | if ($preference->data === $token) { |
||||||
546 | $user = $preference->user; |
||||||
547 | } |
||||||
548 | } |
||||||
549 | if (null === $user) { |
||||||
550 | throw new FireflyException('Invalid token.'); |
||||||
551 | } |
||||||
552 | |||||||
553 | // found user.which email address to return to? |
||||||
554 | $set = app('preferences')->beginsWith($user, 'previous_email_'); |
||||||
555 | /** @var string $match */ |
||||||
556 | $match = null; |
||||||
557 | foreach ($set as $entry) { |
||||||
558 | $hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $entry->data)); |
||||||
559 | if ($hashed === $hash) { |
||||||
560 | $match = $entry->data; |
||||||
561 | break; |
||||||
562 | } |
||||||
563 | } |
||||||
564 | if (null === $match) { |
||||||
565 | throw new FireflyException('Invalid token.'); |
||||||
566 | } |
||||||
567 | // change user back |
||||||
568 | // now actually update user: |
||||||
569 | $repository->changeEmail($user, $match); |
||||||
570 | $repository->unblockUser($user); |
||||||
571 | |||||||
572 | // return to login. |
||||||
573 | session()->flash('success', (string) trans('firefly.login_with_old_email')); |
||||||
574 | |||||||
575 | return redirect(route('login')); |
||||||
576 | } |
||||||
577 | |||||||
578 | /** |
||||||
579 | * TODO duplicate code. |
||||||
580 | * |
||||||
581 | * @param string $mfaCode |
||||||
582 | */ |
||||||
583 | private function addToMFAHistory(string $mfaCode): void |
||||||
584 | { |
||||||
585 | /** @var array $mfaHistory */ |
||||||
586 | $mfaHistory = app('preferences')->get('mfa_history', [])->data; |
||||||
587 | $entry = [ |
||||||
588 | 'time' => time(), |
||||||
589 | 'code' => $mfaCode, |
||||||
590 | ]; |
||||||
591 | $mfaHistory[] = $entry; |
||||||
592 | |||||||
593 | app('preferences')->set('mfa_history', $mfaHistory); |
||||||
594 | $this->filterMFAHistory(); |
||||||
595 | } |
||||||
596 | |||||||
597 | /** |
||||||
598 | * Remove old entries from the preferences array. |
||||||
599 | */ |
||||||
600 | private function filterMFAHistory(): void |
||||||
601 | { |
||||||
602 | /** @var array $mfaHistory */ |
||||||
603 | $mfaHistory = app('preferences')->get('mfa_history', [])->data; |
||||||
604 | $newHistory = []; |
||||||
605 | $now = time(); |
||||||
606 | foreach ($mfaHistory as $entry) { |
||||||
607 | $time = $entry['time']; |
||||||
608 | $code = $entry['code']; |
||||||
609 | if ($now - $time <= 300) { |
||||||
610 | $newHistory[] = [ |
||||||
611 | 'time' => $time, |
||||||
612 | 'code' => $code, |
||||||
613 | ]; |
||||||
614 | } |
||||||
615 | } |
||||||
616 | app('preferences')->set('mfa_history', $newHistory); |
||||||
617 | } |
||||||
618 | } |
||||||
619 |