migrate to gtea from bistbucket
This commit is contained in:
@@ -0,0 +1,597 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user