Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
VerificationController
0.00% covered (danger)
0.00%
0 / 72
0.00% covered (danger)
0.00%
0 / 3
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 verifyByCode
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
72
 resend
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace App\Http\Controllers\v1\UserAuth;
4
5use App\Http\Controllers\Controller;
6use App\Http\Models\Auth\EmailValidationTokens;
7use App\Http\Models\Auth\User;
8use App\Http\Services\EmailVerificationService;
9use Illuminate\Auth\Access\AuthorizationException;
10use Illuminate\Foundation\Auth\VerifiesEmails;
11use Illuminate\Http\JsonResponse;
12use Illuminate\Http\Request;
13use Illuminate\Support\Facades\Cache;
14use MongoDB\BSON\UTCDateTime;
15
16class VerificationController extends Controller
17{
18    use VerifiesEmails;
19
20    public function __construct(
21        private readonly EmailVerificationService $emailVerificationService
22    ) {}
23
24    public function verifyByCode(Request $request): JsonResponse
25    {
26        $validation = [
27            'email' => 'required|string|email',
28            'verification_code' => 'required|string',
29        ];
30        $validatedData = $request->validate($validation);
31
32        $user = User::where('email', $validatedData['email'])->first();
33
34        if (! $user) {
35            throw new AuthorizationException;
36        }
37
38        if ($user->hasVerifiedEmail()) {
39            return response()->json([
40                'error' => 'Email has already been verified.',
41                'error_type' => 'already_verified',
42            ])->setStatusCode(200);
43        }
44
45        $cacheKey = "verification_code_{$user->id}";
46        $attemptsKey = "verification_attempts_{$user->id}";
47
48        $storedCode = Cache::get($cacheKey);
49        $attempts = Cache::get($attemptsKey, 0);
50
51        if (! $storedCode) {
52            return response()->json([
53                'error' => 'Your code has expired.',
54                'error_type' => 'expired_code',
55                'attempts_left' => 0,
56            ], 400);
57        }
58
59        $verification_code = $validatedData['verification_code'];
60
61        if ($attempts >= 5) {
62            Cache::forget($cacheKey);
63            Cache::forget($attemptsKey);
64
65            return response()->json([
66                'error' => "You've exceeded the number of attempts.",
67                'error_type' => 'expired_code',
68                'attempts_left' => 0,
69            ], 403);
70        }
71
72        $token = EmailValidationTokens::where('email', $validatedData['email'])
73            ->where('used', '!=', true)
74            ->latest()
75            ->first();
76
77        if (! $token || (int) $token->token !== (int) $storedCode) {
78            return response()->json([
79                'error' => 'Your code has expired.',
80                'error_type' => 'expired_code',
81                'attempts_left' => 0,
82            ], 400);
83        }
84
85        $token->increment('attempts');
86        Cache::increment($attemptsKey);
87
88        if ($verification_code != $storedCode) {
89            return response()->json([
90                'error' => 'Code is invalid or expired.',
91                'error_type' => 'invalid_code',
92                'attempts_left' => 5 - ($attempts + 1),
93            ], 400);
94        }
95
96        Cache::forget($cacheKey);
97        Cache::forget($attemptsKey);
98
99        $token->used = true;
100        $token->save();
101        $user->email_verified_at = new UTCDateTime(now()->getTimestamp() * 1000);
102        $user->save(); // triggers UserObserver::updated() → cache invalidation
103
104        $user->sendWelcomeNotification();
105
106        return response()->json(['message' => 'Email has been verified.']);
107    }
108
109    public function resend(Request $request): JsonResponse
110    {
111        $data = $request->validate([
112            'email' => 'required|string|email',
113        ]);
114
115        $user = User::where('email', $data['email'])->first();
116
117        if (! $user) {
118            throw new AuthorizationException;
119        }
120
121        if ($user->hasVerifiedEmail()) {
122            return response()->json([
123                'error' => 'Email has already been verified.',
124                'error_type' => 'already_verified',
125            ])->setStatusCode(200);
126        }
127
128        $this->emailVerificationService->generateToken($data['email']);
129
130        return response()->json(['message' => 'We have e-mailed your verification link!']);
131    }
132}