reset.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. <?php
  2. declare(strict_types=1);
  3. use App\App\Bootstrap;
  4. use App\Security\Csrf;
  5. use App\Security\RateLimiter;
  6. use App\Storage\FileSystem;
  7. use App\Storage\JsonStore;
  8. require dirname(__DIR__) . '/src/autoload.php';
  9. Bootstrap::init();
  10. if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  11. Bootstrap::jsonResponse([
  12. 'ok' => false,
  13. 'message' => Bootstrap::appMessage('common.method_not_allowed'),
  14. ], 405);
  15. }
  16. $csrf = $_POST['csrf'] ?? '';
  17. if (!Csrf::validate(is_string($csrf) ? $csrf : null)) {
  18. Bootstrap::jsonResponse([
  19. 'ok' => false,
  20. 'message' => Bootstrap::appMessage('common.invalid_csrf'),
  21. ], 419);
  22. }
  23. if (trim((string) ($_POST['website'] ?? '')) !== '') {
  24. Bootstrap::jsonResponse([
  25. 'ok' => false,
  26. 'message' => Bootstrap::appMessage('common.request_blocked'),
  27. ], 400);
  28. }
  29. $email = strtolower(trim((string) ($_POST['email'] ?? '')));
  30. if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
  31. Bootstrap::jsonResponse([
  32. 'ok' => false,
  33. 'message' => Bootstrap::appMessage('common.invalid_email'),
  34. ], 422);
  35. }
  36. $app = Bootstrap::config('app');
  37. $limiter = new RateLimiter();
  38. $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
  39. $rateKey = sprintf('reset:%s:%s', $ip, $email);
  40. if (!$limiter->allow($rateKey, (int) $app['rate_limit']['requests'], (int) $app['rate_limit']['window_seconds'])) {
  41. Bootstrap::jsonResponse([
  42. 'ok' => false,
  43. 'message' => Bootstrap::appMessage('reset.rate_limited'),
  44. ], 429);
  45. }
  46. $store = new JsonStore();
  47. try {
  48. $result = $store->withEmailLock($email, static function () use ($store, $app, $email): array {
  49. $hadDraft = $store->getDraft($email) !== null;
  50. $submission = $store->getSubmissionByEmail($email);
  51. $hadSubmission = $submission !== null;
  52. if ($hadSubmission) {
  53. return [
  54. 'ok' => false,
  55. 'status' => 409,
  56. 'message' => Bootstrap::appMessage('reset.already_submitted'),
  57. 'had_draft' => $hadDraft,
  58. 'had_submission' => true,
  59. ];
  60. }
  61. $store->deleteDraft($email);
  62. $uploadDir = rtrim((string) $app['storage']['uploads'], '/') . '/' . $store->emailKey($email);
  63. FileSystem::removeTree($uploadDir);
  64. return [
  65. 'ok' => true,
  66. 'status' => 200,
  67. 'had_draft' => $hadDraft,
  68. 'had_submission' => $hadSubmission,
  69. ];
  70. });
  71. } catch (Throwable $e) {
  72. Bootstrap::log('app', 'reset error: ' . $e->getMessage());
  73. Bootstrap::jsonResponse([
  74. 'ok' => false,
  75. 'message' => Bootstrap::appMessage('reset.delete_error'),
  76. ], 500);
  77. }
  78. if (($result['ok'] ?? false) !== true) {
  79. Bootstrap::jsonResponse([
  80. 'ok' => false,
  81. 'message' => (string) ($result['message'] ?? Bootstrap::appMessage('reset.delete_error')),
  82. 'had_draft' => (bool) ($result['had_draft'] ?? false),
  83. 'had_submission' => (bool) ($result['had_submission'] ?? false),
  84. ], (int) ($result['status'] ?? 422));
  85. }
  86. Bootstrap::jsonResponse([
  87. 'ok' => true,
  88. 'message' => Bootstrap::appMessage('reset.success'),
  89. 'had_draft' => (bool) ($result['had_draft'] ?? false),
  90. 'had_submission' => (bool) ($result['had_submission'] ?? false),
  91. ]);