- Added localization support for API responses in English and Spanish. - Introduced a middleware to set the API locale based on the Accept-Language header. - Updated various exception messages and validation responses to use localized strings. - Created new language files for English and Spanish translations. - Refactored existing code to replace hardcoded messages with localized strings. - Added tests to verify localization functionality and response correctness.
174 lines
6.2 KiB
PHP
174 lines
6.2 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Auth\Services;
|
|
|
|
use App\Domains\Auth\Models\User;
|
|
use App\Domains\Notification\Events\UserRegistered;
|
|
use App\Domains\Tenant\Models\Tenant;
|
|
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Laravel\Socialite\Contracts\User as SocialiteUser;
|
|
use Laravel\Socialite\Facades\Socialite;
|
|
|
|
class GoogleAuthService
|
|
{
|
|
public function redirect(Request $request): RedirectResponse
|
|
{
|
|
$tenantCode = $request->string('tenant')->toString();
|
|
$returnUrl = $request->string('return_url')->toString();
|
|
|
|
$tenant = Tenant::query()->where('codigo', $tenantCode)->first();
|
|
|
|
if (! $tenant || ! $this->isTenantReturnUrl($returnUrl, $tenant)) {
|
|
throw ValidationException::withMessages([
|
|
'tenant' => __('api.auth.invalid_tenant_or_return_url'),
|
|
]);
|
|
}
|
|
|
|
$state = (string) Str::uuid();
|
|
Cache::put("google-oauth-context:{$state}", [
|
|
'tenant_codigo' => $tenant->codigo,
|
|
'return_url' => rtrim($returnUrl, '/'),
|
|
], now()->addMinutes(10));
|
|
|
|
return Socialite::driver('google')
|
|
->scopes(['openid', 'profile', 'email'])
|
|
->stateless()
|
|
->with(['state' => $state])
|
|
->redirect();
|
|
}
|
|
|
|
public function callback(Request $request): RedirectResponse
|
|
{
|
|
$state = $request->string('state')->toString();
|
|
|
|
/** @var array{tenant_codigo?: string, return_url?: string}|null $context */
|
|
$context = Str::isUuid($state) ? Cache::pull("google-oauth-context:{$state}") : null;
|
|
|
|
if (! is_array($context) || ! isset($context['tenant_codigo'], $context['return_url'])) {
|
|
abort(400, __('api.auth.request_expired'));
|
|
}
|
|
|
|
$tenant = Tenant::query()->where('codigo', $context['tenant_codigo'])->first();
|
|
|
|
if (! $tenant || ! $this->isTenantReturnUrl($context['return_url'], $tenant)) {
|
|
abort(400, __('api.auth.invalid_return_url'));
|
|
}
|
|
|
|
/** @var SocialiteUser $googleUser */
|
|
$googleUser = Socialite::driver('google')->stateless()->user();
|
|
$user = $this->resolveUser($googleUser, $tenant);
|
|
$token = $user->createToken(
|
|
'google-oauth',
|
|
['*'],
|
|
now()->addMinutes((int) config('sanctum.expiration')),
|
|
)->plainTextToken;
|
|
|
|
$exchangeCode = (string) Str::uuid();
|
|
Cache::put("google-oauth-exchange:{$exchangeCode}", [
|
|
'user_id' => $user->id,
|
|
'token' => $token,
|
|
'tenant_codigo' => $tenant->codigo,
|
|
], now()->addMinutes(5));
|
|
|
|
return redirect()->to($context['return_url'].'/login?'.http_build_query([
|
|
'oauth_code' => $exchangeCode,
|
|
]));
|
|
}
|
|
|
|
/** @return array{user: User, token: string, tenant_codigo: string} */
|
|
public function exchange(string $exchangeCode, string $tenantCodigo): array
|
|
{
|
|
/** @var array{user_id: int, token: string, tenant_codigo?: string}|null $authentication */
|
|
$authentication = Cache::pull("google-oauth-exchange:{$exchangeCode}");
|
|
|
|
if (! $authentication) {
|
|
throw ValidationException::withMessages([
|
|
'oauth_code' => __('api.auth.oauth_code_expired'),
|
|
]);
|
|
}
|
|
|
|
if (($authentication['tenant_codigo'] ?? null) !== $tenantCodigo) {
|
|
throw ValidationException::withMessages([
|
|
'tenant_codigo' => __('api.auth.tenant_mismatch'),
|
|
]);
|
|
}
|
|
|
|
return [
|
|
'user' => User::query()->findOrFail($authentication['user_id']),
|
|
'token' => $authentication['token'],
|
|
'tenant_codigo' => $tenantCodigo,
|
|
];
|
|
}
|
|
|
|
private function resolveUser(SocialiteUser $googleUser, Tenant $tenant): User
|
|
{
|
|
$googleId = $googleUser->getId();
|
|
$email = $googleUser->getEmail();
|
|
|
|
if (! is_string($googleId) || $googleId === '' || ! is_string($email) || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
throw ValidationException::withMessages([
|
|
'google' => __('api.auth.google_email_required'),
|
|
]);
|
|
}
|
|
|
|
$rawUser = $googleUser instanceof \Laravel\Socialite\Two\User ? $googleUser->getRaw() : [];
|
|
$emailVerified = $rawUser['email_verified'] ?? $rawUser['verified_email'] ?? false;
|
|
if (! in_array($emailVerified, [true, 'true', 1, '1'], true)) {
|
|
throw ValidationException::withMessages([
|
|
'google' => __('api.auth.google_email_unverified'),
|
|
]);
|
|
}
|
|
|
|
$user = User::query()->where('google_id', $googleId)->first();
|
|
if ($user) {
|
|
return $user;
|
|
}
|
|
|
|
$user = User::query()->where('email', $email)->first();
|
|
if ($user) {
|
|
$user->forceFill(['google_id' => $googleId])->save();
|
|
|
|
return $user;
|
|
}
|
|
|
|
$name = $googleUser->getName();
|
|
$user = User::query()->create([
|
|
'nombre_apellido' => is_string($name) && $name !== '' ? $name : $email,
|
|
'email' => $email,
|
|
'email_verified_at' => now(),
|
|
'google_id' => $googleId,
|
|
'password' => Str::password(64),
|
|
]);
|
|
|
|
UserRegistered::dispatch($user, $tenant->codigo);
|
|
|
|
return $user;
|
|
}
|
|
|
|
private function isTenantReturnUrl(string $returnUrl, Tenant $tenant): bool
|
|
{
|
|
$parts = parse_url($returnUrl);
|
|
if (! is_array($parts)
|
|
|| ! isset($parts['scheme'], $parts['host'])
|
|
|| isset($parts['user'], $parts['pass'], $parts['query'], $parts['fragment'])
|
|
|| ($parts['path'] ?? '') !== '') {
|
|
return false;
|
|
}
|
|
|
|
$scheme = strtolower($parts['scheme']);
|
|
$host = TenantDomainNormalizer::normalize($parts['host']);
|
|
$tenantDomain = TenantDomainNormalizer::normalize($tenant->dominio);
|
|
|
|
if ($host === null || $tenantDomain === null || $host !== $tenantDomain) {
|
|
return false;
|
|
}
|
|
|
|
return $scheme === 'https' || ($scheme === 'http' && in_array($host, ['localhost', '127.0.0.1'], true));
|
|
}
|
|
}
|