Files

598 lines
16 KiB
PHP

<?php
declare(strict_types=1);
namespace Modules\RestaurantDelivery\Services\Firebase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Auth;
use Kreait\Firebase\Auth\CreateRequest;
use Kreait\Firebase\Exception\Auth\FailedToVerifyToken;
use Kreait\Firebase\Exception\FirebaseException;
use Kreait\Firebase\Factory;
/**
* Firebase Authentication Service
*
* Handles user authentication, custom tokens, and claims for role-based access.
*/
class FirebaseAuthService
{
protected ?Auth $auth = null;
protected Factory $factory;
protected array $config;
public function __construct()
{
$this->config = config('restaurant-delivery.firebase');
$this->initializeFirebase();
}
protected function initializeFirebase(): void
{
if (! $this->config['enabled']) {
return;
}
try {
$this->factory = (new Factory)
->withServiceAccount($this->config['credentials_path']);
} catch (\Exception $e) {
Log::error('Firebase Auth initialization failed', [
'error' => $e->getMessage(),
]);
}
}
public function getAuth(): ?Auth
{
if (! $this->auth && isset($this->factory)) {
$this->auth = $this->factory->createAuth();
}
return $this->auth;
}
/*
|--------------------------------------------------------------------------
| Custom Token Generation
|--------------------------------------------------------------------------
*/
/**
* Create a custom token for a user with custom claims
*
* @param string $uid User ID
* @param array $claims Custom claims (role, permissions, etc.)
* @return string|null JWT token
*/
public function createCustomToken(string $uid, array $claims = []): ?string
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
$customToken = $auth->createCustomToken($uid, $claims);
return $customToken->toString();
} catch (FirebaseException $e) {
Log::error('Failed to create custom token', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* Create a custom token for a rider with appropriate claims
*/
public function createRiderToken(string $riderId, array $additionalClaims = []): ?string
{
$claims = array_merge([
'role' => 'rider',
'rider_id' => $riderId,
'can_update_location' => true,
'can_accept_deliveries' => true,
], $additionalClaims);
return $this->createCustomToken($riderId, $claims);
}
/**
* Create a custom token for a restaurant with appropriate claims
*/
public function createRestaurantToken(string $restaurantId, array $additionalClaims = []): ?string
{
$claims = array_merge([
'role' => 'restaurant',
'restaurant_id' => $restaurantId,
'restaurant' => true,
'can_create_deliveries' => true,
'can_assign_riders' => true,
], $additionalClaims);
return $this->createCustomToken($restaurantId, $claims);
}
/**
* Create a custom token for a customer with appropriate claims
*/
public function createCustomerToken(string $customerId, array $additionalClaims = []): ?string
{
$claims = array_merge([
'role' => 'customer',
'customer_id' => $customerId,
'can_track_deliveries' => true,
'can_rate_riders' => true,
], $additionalClaims);
return $this->createCustomToken($customerId, $claims);
}
/**
* Create a custom token for an admin with full permissions
*/
public function createAdminToken(string $adminId, array $additionalClaims = []): ?string
{
$claims = array_merge([
'role' => 'admin',
'admin' => true,
'admin_id' => $adminId,
'full_access' => true,
], $additionalClaims);
return $this->createCustomToken($adminId, $claims);
}
/*
|--------------------------------------------------------------------------
| Token Verification
|--------------------------------------------------------------------------
*/
/**
* Verify an ID token and return the decoded claims
*/
public function verifyIdToken(string $idToken): ?array
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
$verifiedToken = $auth->verifyIdToken($idToken);
return [
'uid' => $verifiedToken->claims()->get('sub'),
'email' => $verifiedToken->claims()->get('email'),
'email_verified' => $verifiedToken->claims()->get('email_verified'),
'claims' => $verifiedToken->claims()->all(),
];
} catch (FailedToVerifyToken $e) {
Log::warning('Failed to verify ID token', [
'error' => $e->getMessage(),
]);
return null;
} catch (FirebaseException $e) {
Log::error('Firebase error during token verification', [
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* Check if token has specific role
*/
public function hasRole(string $idToken, string $role): bool
{
$decoded = $this->verifyIdToken($idToken);
if (! $decoded) {
return false;
}
return ($decoded['claims']['role'] ?? null) === $role;
}
/**
* Check if token has admin access
*/
public function isAdmin(string $idToken): bool
{
$decoded = $this->verifyIdToken($idToken);
if (! $decoded) {
return false;
}
return ($decoded['claims']['admin'] ?? false) === true;
}
/*
|--------------------------------------------------------------------------
| Custom Claims Management
|--------------------------------------------------------------------------
*/
/**
* Set custom claims for a user
*/
public function setCustomClaims(string $uid, array $claims): bool
{
try {
$auth = $this->getAuth();
if (! $auth) {
return false;
}
$auth->setCustomUserClaims($uid, $claims);
// Invalidate cached claims
Cache::forget("firebase_claims_{$uid}");
return true;
} catch (FirebaseException $e) {
Log::error('Failed to set custom claims', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return false;
}
}
/**
* Get user's custom claims
*/
public function getCustomClaims(string $uid): array
{
// Try cache first
$cached = Cache::get("firebase_claims_{$uid}");
if ($cached !== null) {
return $cached;
}
try {
$auth = $this->getAuth();
if (! $auth) {
return [];
}
$user = $auth->getUser($uid);
$claims = $user->customClaims ?? [];
// Cache for 5 minutes
Cache::put("firebase_claims_{$uid}", $claims, 300);
return $claims;
} catch (FirebaseException $e) {
Log::error('Failed to get custom claims', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return [];
}
}
/**
* Add role to user
*/
public function addRole(string $uid, string $role): bool
{
$claims = $this->getCustomClaims($uid);
$claims['role'] = $role;
// Add role-specific flags
switch ($role) {
case 'admin':
$claims['admin'] = true;
break;
case 'restaurant':
$claims['restaurant'] = true;
break;
case 'rider':
$claims['rider'] = true;
break;
case 'customer':
$claims['customer'] = true;
break;
}
return $this->setCustomClaims($uid, $claims);
}
/**
* Remove role from user
*/
public function removeRole(string $uid, string $role): bool
{
$claims = $this->getCustomClaims($uid);
if (isset($claims['role']) && $claims['role'] === $role) {
unset($claims['role']);
}
// Remove role-specific flags
unset($claims[$role]);
return $this->setCustomClaims($uid, $claims);
}
/*
|--------------------------------------------------------------------------
| User Management
|--------------------------------------------------------------------------
*/
/**
* Create a new Firebase user
*/
public function createUser(array $properties): ?array
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
$request = CreateRequest::new();
if (isset($properties['email'])) {
$request = $request->withEmail($properties['email']);
}
if (isset($properties['password'])) {
$request = $request->withPassword($properties['password']);
}
if (isset($properties['phone'])) {
$request = $request->withPhoneNumber($properties['phone']);
}
if (isset($properties['display_name'])) {
$request = $request->withDisplayName($properties['display_name']);
}
if (isset($properties['photo_url'])) {
$request = $request->withPhotoUrl($properties['photo_url']);
}
if (isset($properties['disabled'])) {
$request = $properties['disabled'] ? $request->markAsDisabled() : $request->markAsEnabled();
}
if (isset($properties['email_verified'])) {
$request = $properties['email_verified'] ? $request->markEmailAsVerified() : $request->markEmailAsUnverified();
}
if (isset($properties['uid'])) {
$request = $request->withUid($properties['uid']);
}
$user = $auth->createUser($request);
return [
'uid' => $user->uid,
'email' => $user->email,
'phone' => $user->phoneNumber,
'display_name' => $user->displayName,
'photo_url' => $user->photoUrl,
'disabled' => $user->disabled,
'email_verified' => $user->emailVerified,
];
} catch (FirebaseException $e) {
Log::error('Failed to create Firebase user', [
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* Get user by UID
*/
public function getUser(string $uid): ?array
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
$user = $auth->getUser($uid);
return [
'uid' => $user->uid,
'email' => $user->email,
'phone' => $user->phoneNumber,
'display_name' => $user->displayName,
'photo_url' => $user->photoUrl,
'disabled' => $user->disabled,
'email_verified' => $user->emailVerified,
'custom_claims' => $user->customClaims,
'created_at' => $user->metadata->createdAt,
'last_login' => $user->metadata->lastLoginAt,
];
} catch (FirebaseException $e) {
Log::error('Failed to get Firebase user', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* Get user by email
*/
public function getUserByEmail(string $email): ?array
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
$user = $auth->getUserByEmail($email);
return [
'uid' => $user->uid,
'email' => $user->email,
'phone' => $user->phoneNumber,
'display_name' => $user->displayName,
'disabled' => $user->disabled,
];
} catch (FirebaseException $e) {
return null;
}
}
/**
* Update user properties
*/
public function updateUser(string $uid, array $properties): bool
{
try {
$auth = $this->getAuth();
if (! $auth) {
return false;
}
$auth->updateUser($uid, $properties);
return true;
} catch (FirebaseException $e) {
Log::error('Failed to update Firebase user', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return false;
}
}
/**
* Disable a user
*/
public function disableUser(string $uid): bool
{
return $this->updateUser($uid, ['disabled' => true]);
}
/**
* Enable a user
*/
public function enableUser(string $uid): bool
{
return $this->updateUser($uid, ['disabled' => false]);
}
/**
* Delete a user
*/
public function deleteUser(string $uid): bool
{
try {
$auth = $this->getAuth();
if (! $auth) {
return false;
}
$auth->deleteUser($uid);
// Clear cached claims
Cache::forget("firebase_claims_{$uid}");
return true;
} catch (FirebaseException $e) {
Log::error('Failed to delete Firebase user', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return false;
}
}
/*
|--------------------------------------------------------------------------
| Password Management
|--------------------------------------------------------------------------
*/
/**
* Generate password reset link
*/
public function generatePasswordResetLink(string $email): ?string
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
return $auth->getPasswordResetLink($email);
} catch (FirebaseException $e) {
Log::error('Failed to generate password reset link', [
'email' => $email,
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* Generate email verification link
*/
public function generateEmailVerificationLink(string $email): ?string
{
try {
$auth = $this->getAuth();
if (! $auth) {
return null;
}
return $auth->getEmailVerificationLink($email);
} catch (FirebaseException $e) {
Log::error('Failed to generate email verification link', [
'email' => $email,
'error' => $e->getMessage(),
]);
return null;
}
}
/**
* Revoke all refresh tokens for a user
*/
public function revokeRefreshTokens(string $uid): bool
{
try {
$auth = $this->getAuth();
if (! $auth) {
return false;
}
$auth->revokeRefreshTokens($uid);
return true;
} catch (FirebaseException $e) {
Log::error('Failed to revoke refresh tokens', [
'uid' => $uid,
'error' => $e->getMessage(),
]);
return false;
}
}
}