Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 364
0.00% covered (danger)
0.00%
0 / 24
CRAP
0.00% covered (danger)
0.00%
0 / 1
LoginController
0.00% covered (danger)
0.00%
0 / 364
0.00% covered (danger)
0.00%
0 / 24
4422
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
 login
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 attemptLogin
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
210
 generateJwt
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
2
 base64UrlEncode
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validateCompanyLicense
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getLoginMeta
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sendLockoutResponse
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 logout
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getOAuthClient
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 refresh
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
72
 getSubscriptionsData
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getSubscriptionsStatus
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
20
 getProductById
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getProductByName
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getInvoiceById
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 setNewSubscription
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 setCancelSubscription
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 storeSubscription
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 extensionSync
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 decodeJwt
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 loginExtension
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
72
 masqueradeUser
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Controllers\v1\Auth;
4
5use App\Http\Models\RefreshTokenTemp;
6use Illuminate\Support\Facades\Cache;
7use Stripe\StripeClient;
8use Illuminate\Http\Request;
9use App\Events\User\LoggedIn;
10use Illuminate\Http\Response;
11use App\Events\User\LoggedOut;
12use App\Http\Models\Auth\User;
13use App\Http\Models\Subscription;
14use Illuminate\Http\JsonResponse;
15use App\Http\Controllers\Controller;
16use App\Http\Models\Passport\Client;
17use Illuminate\Support\Facades\Lang;
18use Illuminate\Support\Facades\Config;
19use App\Http\Models\Admin\AdminUserInvitation;
20use App\Http\Models\Admin\CompanyLicenses;
21use App\Http\Models\UserInfo;
22use App\Http\Requests\MasqueradeUserFormRequest;
23use App\Http\Services\Admin\Users\AdminUsersService;
24use App\Services\UserInfo\UserInfoService;
25use Illuminate\Foundation\Auth\AuthenticatesUsers;
26use Carbon\Carbon;
27use Illuminate\Support\Facades\Log;
28
29class LoginController extends Controller
30{
31    use AuthenticatesUsers;
32
33    public function __construct(
34        private AdminUsersService $adminUsersService,
35        private UserInfoService $userInfoService
36    ) {}
37
38    /**
39     * @OA\Post  (
40     *     tags={"FlyMSG Project"},
41     *     path="/romeo/api/v1/user/auth/login",
42     *     summary="user login",
43     *
44     *     @OA\RequestBody(
45     *         required=true,
46     *
47     *         @OA\MediaType(
48     *             mediaType="application/json",
49     *
50     *             @OA\Schema(
51     *                 type="object",
52     *
53     *                 @OA\Property(
54     *                     property="email",
55     *                     description="User email",
56     *                     type="string",
57     *                     example="ihamzehald@gmail.com"
58     *                 ),
59     *                 @OA\Property(
60     *                     property="password",
61     *                     description="User password",
62     *                     type="string",
63     *                     example="larapoints123"
64     *                 ),
65     *             )
66     *         )
67     *     ),
68     *
69     *     @OA\Response(
70     *          response="401",
71     *          description="Unauthorized",
72     *          content={
73     *
74     *             @OA\MediaType(
75     *                 mediaType="application/json",
76     *
77     *                 @OA\Schema(
78     *
79     *                     @OA\Property(
80     *                         property="error",
81     *                         type="string",
82     *                         description="Error message",
83     *                     ),
84     *                     @OA\Property(
85     *                         property="max_attempts",
86     *                         type="string",
87     *                         description="Max attempts"
88     *                     ),
89     *                     @OA\Property(
90     *                         property="failed_attempts",
91     *                         type="string",
92     *                         description="Failed attempts",
93     *                     ),
94     *                     example={
95     *                          "error": "These credentials do not match our records.",
96     *                          "max_attempts": 5,
97     *                          "failed_attempts": "1"
98     *                     }
99     *                 )
100     *             )
101     *         }
102     *     ),
103     *
104     *  @OA\Response(
105     *         response="200",
106     *         description="ok",
107     *         content={
108     *
109     *             @OA\MediaType(
110     *                 mediaType="application/json",
111     *
112     *                 @OA\Schema(
113     *
114     *                     @OA\Property(
115     *                         property="access_token",
116     *                         type="string",
117     *                         description="JWT access token"
118     *                     ),
119     *                     @OA\Property(
120     *                         property="token_type",
121     *                         type="string",
122     *                         description="Token type"
123     *                     ),
124     *                     @OA\Property(
125     *                         property="expires_in",
126     *                         type="array",
127     *                         description="Token expiration in miliseconds",
128     *
129     *                         @OA\Items
130     *                     ),
131     *                     example={
132     *                          "result": {
133     *                              "token_type": "Bearer",
134     *                              "expires_in": 86399,
135     *                              "access_token": "eyJ...",
136     *                              "session_expires_in": 172800,
137     *                              "user_details": {
138     *                                  "first_name": "JorgeOscar",
139     *                                   "last_name": "Abrehu",
140     *                                   "email": "jabrehu@gmail.com",
141     *                                   "signup_source": "email",
142     *                                   "updated_at": 1647998250,
143     *                                   "created_at": 1647266869,
144     *                                   "last_login": "623a752a9b3ba409cd44d934",
145     *                                   "id": "622f4c3517e1a60b263ef7d2"
146     *                              }
147     *                          },
148     *                          "subscription_details": null
149     *                     }
150     *                 )
151     *             )
152     *         }
153     *      ),
154     * )
155     */
156    public function login(Request $request)
157    {
158        $this->validateLogin($request);
159        if (
160            method_exists($this, 'hasTooManyLoginAttempts') &&
161            $this->hasTooManyLoginAttempts($request)
162        ) {
163            $this->fireLockoutEvent($request);
164
165            return $this->sendLockoutResponse($request);
166        }
167
168        return $this->attemptLogin($request);
169    }
170
171    public function attemptLogin(Request $request): JsonResponse
172    {
173        $username = $request->get($this->username());
174        $password = $request->get('password');
175
176        $user = $this->getUser($username);
177
178        if (filled($user?->company_id) && !$this->validateCompanyLicense($user->company_id)) {
179            return response()->json([
180                "code" => "NO_ACTIVE_LICENSE",
181                "error" => "The company doesn't have any active license.",
182                "message" => "The company doesn't have any active license. Please contact the administrator."
183            ], Response::HTTP_UNPROCESSABLE_ENTITY);
184        }
185
186        if (filled($user?->deleted_at) || filled($user?->deactivated_at)) {
187            return response()->json([
188                "code" => "DEACTIVATED",
189                "error" => "Account is deactivated",
190                "message" => "Account is deactivated",
191                "admin_email" =>  $user?->company?->pocs()?->first()?->email
192            ], Response::HTTP_UNPROCESSABLE_ENTITY);
193        }
194
195        $invitation = AdminUserInvitation::firstWhere('email', $username);
196
197        //If user is invited, hasn't reset password and temporary password is expired
198        if (filled($invitation) && (now()->greaterThan($user?->temp_password_expiry) || now()->greaterThan($invitation?->temp_password_expiry))) {
199            return response()->json([
200                "code" => "EXPIRED_LOGIN",
201                "error" => "The invitation has expired. Please generate a new one.",
202                "message" => "The invitation has expired. Please generate a new one."
203            ], Response::HTTP_UNPROCESSABLE_ENTITY);
204        }
205
206        if (!$user && $invitation) {
207            $user = $this->adminUsersService->createUserForInvitationLogin($invitation);
208        }
209
210        $client = $this->getOAuthClient();
211        $authRequest = Request::create(route('passport.token'), 'POST', [
212            'grant_type' => 'password',
213            'client_id' => $client->id,
214            'client_secret' => $client->secret,
215            'username' => $username,
216            'password' => $password,
217            'scope' => '',
218        ]);
219
220        $response = retry(3, function () use ($authRequest) {
221            return app()->handle($authRequest);
222        }, 100);
223        // $response = app()->handle($authRequest);
224        $data = json_decode($response->getContent(), true);
225
226        if (empty($data) || isset($data['error']) || !isset($data['access_token'])) {
227            $this->incrementLoginAttempts($request);
228
229            $data = array_merge($data ?? [], [
230                'error' => $data['error'] ?? trans('auth.failed'),
231                'max_attempts' => $this->maxAttempts(),
232                'failed_attempts' => $this->limiter()->attempts($this->throttleKey($request)),
233                'data' => $data,
234                'response' => $response
235            ]);
236
237            return response()->json($data, Response::HTTP_UNPROCESSABLE_ENTITY);
238        }
239
240        $this->clearLoginAttempts($request);
241
242        $user->is_poc = $user->isPOC();
243
244        $data = array_merge($data, [
245            'session_expires_in' => intval(
246                config('auth.passport.refresh_token_expiry')
247            ),
248            'user_details' => $user,
249            'company' => $user?->company?->slug
250        ]);
251
252        $requireExtension = $request->get('include_extension');
253        if (!$requireExtension) {
254            $request = Request::create(config('app.url') . '/romeo/api/v1/user/auth/login', 'POST', [
255                'email' => $request->email,
256                'password' => $request->password,
257                'include_extension' => true
258            ]);
259
260            $response = app()->handle($request);
261            $extensionData = json_decode($response->getContent(), true);
262
263            $data['extension'] = $extensionData['result'];
264        }
265
266        LoggedIn::dispatch($user, ['email' => $user['email'], 'signin_source' => 'email']);
267
268        return response()->json($data);
269    }
270
271    protected function generateJwt($user)
272    {
273        // Header
274        $header = json_encode([
275            'typ' => 'JWT',
276            'alg' => 'HS256',
277        ]);
278        $base64UrlHeader = $this->base64UrlEncode($header);
279
280        // Payload
281        $now = time();
282        $common = [
283            'iat' => $now,
284            'jti' => md5($now . rand()),
285        ];
286
287        $secret = 'GPn5ch5eLuyKXrtwDcesqkY8NQzYzDHm3HNdmTHAZ08=';
288        $companyName = 'vengreso';
289
290        $user_attributes = [
291            'user_external_id' => $user['id'],
292            'user_email' => $user['email'],
293            'user_first_name' => $user['first_name'],
294            'user_last_name' => $user['last_name'],
295            'company_external_id' => $user['id'],
296            'company_name' => $companyName . '_' . $user['email'],
297            'company_website' => 'https://' . $companyName . $user['email'] . '.com',
298        ];
299
300        $payload = json_encode(array_merge($common, $user_attributes));
301        $base64UrlPayload = $this->base64UrlEncode($payload);
302
303        // JWT
304        $message = $base64UrlHeader . '.' . $base64UrlPayload;
305        $signature = hash_hmac('sha256', $message, $secret, true);
306        $base64UrlSignature = $this->base64UrlEncode($signature);
307        $jwt = $base64UrlHeader . '.' . $base64UrlPayload . '.' . $base64UrlSignature;
308
309        return $jwt;
310    }
311
312    protected function base64UrlEncode($text)
313    {
314        return str_replace(
315            ['+', '/', '='],
316            ['-', '_', ''],
317            base64_encode($text)
318        );
319    }
320
321    protected function getUser($username)
322    {
323        return User::withTrashed()->firstWhere($this->username(), $username);
324    }
325
326    private function validateCompanyLicense(string $companyId): Bool
327    {
328        $companyLicense = CompanyLicenses::where('company_id', $companyId)->active()->first();
329
330        return !$companyLicense ? false : true;
331    }
332
333    protected function getLoginMeta()
334    {
335        return [];
336    }
337
338    protected function sendLockoutResponse(Request $request)
339    {
340        $seconds = $this->limiter()->availableIn(
341            $this->throttleKey($request)
342        );
343
344        $response = [
345            'error' => Lang::get('auth.throttle', [
346                'seconds' => $seconds,
347                'minutes' => ceil($seconds / 60),
348            ]),
349        ];
350
351        return response()->json($response, Response::HTTP_TOO_MANY_REQUESTS);
352    }
353
354    public function logout(Request $request): JsonResponse
355    {
356        $user = $request->user();
357        $user->token()->revoke();
358        $user->token()->delete();
359
360        $data = $request->validate([
361            'description' => 'sometimes',
362            'selectedOption' => 'sometimes'
363        ]);
364
365        LoggedOut::dispatch(
366            $user,
367            $request->has(['description', 'selectedOption']) ? $data : null
368        );
369
370        return response()->json(['message' => 'User has been logged out.']);
371    }
372
373    protected function getOAuthClient()
374    {
375        return Client::firstWhere('password_client', true);
376    }
377
378    public function refresh(Request $request): JsonResponse
379    {
380        $method = $request->method();
381        $url = $request->url();
382        $headers = $request->headers->all();
383        $params = $request->all();
384
385        if ($method != 'GET') {
386            $headers = json_encode($request->headers->all());
387            $params = json_encode($request->all());
388        }
389
390        Log::info('HTTP Request: ' . $method . ' ' . $url, [
391            'headers' => $headers,
392            'params' => $params,
393            'user' => $request->user()
394        ]);
395
396        $request->validate([
397            'refresh_token' => 'required|string',
398        ]);
399
400        $refreshToken = $request->get('refresh_token');
401
402        $data = Cache::get($refreshToken);
403        $exists = !empty($data);
404
405        if (empty($data)) {
406            // wait 3 seconds and try again
407            sleep(3);
408
409            $data = Cache::get($refreshToken);
410            $exists = !empty($data);
411
412            if (empty($data)) {
413                $client = $this->getOAuthClient();
414                $authData = [
415                    'grant_type' => 'refresh_token',
416                    'client_id' => $client->id,
417                    'client_secret' => $client->secret,
418                    'refresh_token' => $refreshToken,
419                    'scope' => '',
420                ];
421
422                $authRequest = Request::create(route('passport.token'), 'POST', $authData);
423                $response = app()->handle($authRequest);
424                $data = json_decode($response->getContent(), true);
425
426                Cache::set($refreshToken, $data, now()->addDays(15));
427            }
428        }
429
430        if (!empty($data)) {
431            $sessionExpiry = Config::get('auth.passport.refresh_token_expiry');
432            $data['session_expires_in'] = intval($sessionExpiry);
433
434            if ($exists) {
435                $dbRefreshToken = RefreshTokenTemp::where('refresh_token', $refreshToken)->first();
436                if ($dbRefreshToken) {
437                    $dbRefreshToken->times_used = $dbRefreshToken->times_used + 1;
438                    $dbRefreshToken->save();
439                } else {
440                    RefreshTokenTemp::create([
441                        'refresh_token' => $refreshToken,
442                        'data' => $data,
443                        'times_used' => 1,
444                    ]);
445                }
446            } else {
447                RefreshTokenTemp::create([
448                    'refresh_token' => $refreshToken,
449                    'data' => $data,
450                    'times_used' => 1,
451                ]);
452            }
453
454            return response()->json($data);
455        } else {
456            $dbRefreshToken = RefreshTokenTemp::where('refresh_token', $refreshToken)->first();
457            if ($dbRefreshToken) {
458                $dbRefreshToken->times_error = $dbRefreshToken->times_error + 1;
459                $dbRefreshToken->save();
460            } else {
461                RefreshTokenTemp::create([
462                    'refresh_token' => $refreshToken,
463                    'data' => $data,
464                    'times_error' => 1,
465                ]);
466            }
467
468            return response()->json($data, Response::HTTP_UNAUTHORIZED);
469        }
470    }
471
472    public function getSubscriptionsData($user_id)
473    {
474        $result = null;
475        $record = Subscription::where(['user_id' => $user_id])->orderBy('created_at', 'desc')->limit(1)->first();
476        if ($record) {
477            $status_plan = $this->getSubscriptionsStatus($record, $user_id);
478            $result['subscription_id'] = $record['subscription_id'];
479            $result['custormer_id'] = $record['custormer_id'];
480            $result['price_id'] = $record['price_id'];
481            $result['status_plan'] = $status_plan;
482            $result['subscription_data'] = json_decode($record['subscription_data'], false);
483        }
484
485        return $result;
486    }
487
488    public function getSubscriptionsStatus($subscription, $user_id): array
489    {
490        $result = [];
491        $stripe_secret = config('services.stripe.secret');
492        $stripe = new StripeClient($stripe_secret);
493        $subscription_details = $stripe->subscriptions->retrieve($subscription['subscription_id']);
494        $current_period_end = $subscription_details['current_period_end'];
495        $latest_invoice = $subscription_details['latest_invoice'];
496        $status = $subscription_details['status'];
497        $product_id = $subscription_details['items']['data'][0]['plan']['product'];
498        $product = $this->getProductById($product_id);
499        $invoice = $this->getInvoiceById($latest_invoice);
500        $freeProduct = $this->getProductByName('Free');
501        $result = [
502            'plan' => $product['name'],
503            'status' => $status,
504            'last_payment_date' => $invoice['created'],
505            'current_period_end' => $current_period_end,
506            'older_plan' => '',
507            'log_message' => $freeProduct,
508        ];
509        if ($status === 'canceled' or $status === 'unpaid' or $status === 'incomplete_expired') {
510            // create new subscription
511            $newSubscription = $this->setNewSubscription($subscription_details, $freeProduct, $user_id);
512            // cancel current subscription
513            $isCancel = $this->setCancelSubscription($subscription_details);
514            $current_period_end = $newSubscription['current_period_end'];
515            $result = [
516                'plan' => $freeProduct['name'],
517                'status' => $status,
518                'last_payment_date' => null,
519                'current_period_end' => $current_period_end,
520                'older_plan' => $product['name'],
521                'log_message' => 'Downgrade to the free plan.',
522            ];
523        }
524
525        return $result;
526    }
527
528    public function getProductById($product_id): \Stripe\Product
529    {
530        $stripe_secret = config('services.stripe.secret');
531        $stripe = new StripeClient($stripe_secret);
532
533        return $stripe->products->retrieve($product_id);
534    }
535
536    public function getProductByName($product_name)
537    {
538        $result = [];
539        $stripe_secret = config('services.stripe.secret');
540        $stripe = new StripeClient($stripe_secret);
541        $products = $stripe->products->all(['limit' => 10]);
542        foreach ($products as $product) {
543            if ($product['name'] === 'Free') {
544                $price = $stripe->prices->all(['limit' => 1, 'product' => $product['id']]);
545                $result = $product;
546                $result['price'] = $price;
547            }
548        }
549
550        return $result;
551    }
552
553    public function getInvoiceById($invoice_id): \Stripe\Invoice
554    {
555        $stripe_secret = config('services.stripe.secret');
556        $stripe = new StripeClient($stripe_secret);
557
558        return $stripe->invoices->retrieve($invoice_id);
559    }
560
561    private function setNewSubscription(\Stripe\Subscription $subscription_details, \Stripe\Product $freeProduct, $user_id): \Stripe\Subscription
562    {
563        try {
564            $stripe_secret = config('services.stripe.secret');
565            $stripe = new StripeClient($stripe_secret);
566            $data = [
567                'customer' => $subscription_details['customer'],
568                'items' => [
569                    ['price' => $freeProduct['price']['data'][0]['id']],
570                ],
571            ];
572            $newSubscription = $stripe->subscriptions->create($data);
573            $this->storeSubscription($newSubscription, $subscription_details['customer'], $user_id);
574            $result = $newSubscription;
575        } catch (\Exception $e) {
576            $result = [];
577        }
578
579        return $result;
580    }
581
582    private function setCancelSubscription(\Stripe\Subscription $subscription_details): bool
583    {
584        $result = true;
585        try {
586            $stripe_secret = config('services.stripe.secret');
587            $stripe = new StripeClient($stripe_secret);
588            $stripe->subscriptions->cancel($subscription_details['id'], []);
589        } catch (\Exception $e) {
590            $result = false;
591        }
592
593        return $result;
594    }
595
596    private function storeSubscription($subscription, $customer, $user_id): bool
597    {
598        try {
599            $subscriptionToString = json_encode($subscription);
600            $customerToString = json_encode($customer);
601            $newSubscription = new Subscription();
602            $newSubscription->subscription_id = $subscription['id'];
603            $newSubscription->user_id = $user_id;
604            $newSubscription->custormer_id = $customer;
605            $newSubscription->price_id = $subscription['items']['data'][0]['price']['id'];
606            $newSubscription->subscription_data = $subscriptionToString;
607            $newSubscription->customer_data = $customerToString;
608            $result = $newSubscription->save();
609        } catch (\Exception $e) {
610            $result = false;
611        }
612
613        return $result;
614    }
615
616    public function extensionSync(Request $request): JsonResponse
617    {
618        $access_token = $request->get('accessToken');
619        $refresh_token = $request->get('refreshToken');
620        $email = $request->get('email');
621        $hubspot_id = $request->get('hubspot_id');
622
623        if ($access_token && $refresh_token) {
624            $data['access_token'] = $access_token;
625            $data['refresh_token'] = $refresh_token;
626            $data['user_details']['email'] = $email;
627            $data['user_details']['hubspot_id'] = $hubspot_id;
628
629            return response()->json($data);
630        }
631
632        return response()->json(['error' => true]);
633    }
634
635    private function decodeJwt($jwt)
636    {
637        $tokenParts = explode('.', $jwt);
638        $header = $tokenParts[0];
639        $payload = $tokenParts[1];
640
641        $decodedHeader = json_decode(base64_decode(strtr($header, '-_', '+/')), true);
642        $decodedPayload = json_decode(base64_decode(strtr($payload, '-_', '+/')), true);
643
644        return [
645            'header' => $decodedHeader,
646            'payload' => $decodedPayload,
647        ];
648    }
649
650    public function loginExtension(Request $request)
651    {
652        $user = $request->user();
653        $tokenResult = $user->createToken('AdminGeneratedToken');
654        $tokenResult->token->expires_at = Carbon::now()->addYear();
655        $tokenResult->token->save();
656        $user['is_poc'] = $user->isPOC();
657
658        $data = [
659            'token_type' => 'Bearer',
660            'access_token' => $tokenResult->accessToken,
661            'expires_in' => $tokenResult->token->expires_at->diffInSeconds(Carbon::now()),
662            'session_expires_in' => intval(
663                config('auth.passport.refresh_token_expiry')
664            ),
665            'user_details' => $user,
666            'company' => $user?->company?->slug
667        ];
668
669        if ($request->header('Browser-type')) {
670            $browser_type = strtolower($request->header('Browser-type'));
671        }
672        if ($request->header('browser-type')) {
673            $browser_type = strtolower($request->header('browser-type'));
674        }
675        if ($request['browser']) {
676            $browser_type = strtolower($request['browser']);
677        }
678
679        if (!empty($browser_type)) {
680            $browser_type = strtolower($browser_type);
681
682            $userInfo = UserInfo::where('email', $user->email)->first();
683            if ($userInfo) {
684                $props = $this->userInfoService->addExtension($userInfo, $browser_type, now());
685
686                $chromeVersion = $request->header('flymsg_extension_version_for_chrome') ?? $request->header('Flymsg_extension_version_for_chrome') ?? $request->header('flymsgextensionversionforchrome') ?? $request->header('flymsgExtensionVersionForChrome');
687                $edgeVersion = $request->header('flymsg_extension_version_for_edge') ?? $request->header('Flymsg_extension_version_for_edge') ?? $request->header('flymsgextensionversionforedge') ?? $request->header('flymsgExtensionVersionForEdge');
688
689                if (empty($chromeVersion) && empty($edgeVersion)) {
690                    Log::info('Extension version not found in headers. User: ' . $user->email, [
691                        'user' => $user,
692                        'headers' => $request->header()
693                    ]);
694                }
695
696                $props['flymsg_extension_version_for_edge'] = $edgeVersion;
697                $props['flymsg_extension_version_for_chrome'] = $chromeVersion;
698
699                $userInfo->update($props);
700            }
701        }
702
703        return response()->json($data);
704    }
705
706    public function masqueradeUser(MasqueradeUserFormRequest $request, User $user)
707    {
708        $tokenResult = $user->createToken('AdminGeneratedToken');
709        $tokenResult->token->expires_at = Carbon::now()->addDay();
710        $tokenResult->token->save();
711        $user['is_poc'] = $user->isPOC();
712
713        $data = [
714            'token_type' => 'Bearer',
715            'access_token' => $tokenResult->accessToken,
716            'expires_in' => $tokenResult->token->expires_at->diffInSeconds(Carbon::now()),
717            'session_expires_in' => intval(
718                config('auth.passport.refresh_token_expiry')
719            ),
720            'user_details' => $user,
721            'company' => $user?->company?->slug
722        ];
723
724        return response()->json($data);
725    }
726}