Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Console/Commands/GrpcServeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function handle(): int
$server->addHttp2Port("{$host}:{$port}");

// Register the User service
$server->handle(app(\App\Grpc\Services\UserServer::class));
$server->handle(app(\App\Grpc\Services\UserGrpcService::class));

$this->info('gRPC User Service started successfully');
$this->info('Listening for requests...');
Expand Down
29 changes: 0 additions & 29 deletions app/Grpc/BaseResponse.php

This file was deleted.

189 changes: 189 additions & 0 deletions app/Grpc/Controllers/AuthController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php

namespace App\Grpc\Controllers;

use App\Grpc\ErrorInfo;
use App\Grpc\FlexibleRequest;
use App\Grpc\FlexibleResponse;
use App\Grpc\Middleware\GrpcAuthMiddleware;
use App\Http\Requests\Users\UserCreateRequest;
use App\Http\Requests\Users\UserReadRequest;
use App\Http\Resources\UserResource;
use App\Services\UserService;
use Exception;
use Google\Protobuf\Any;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response;
use const Grpc\STATUS_INVALID_ARGUMENT;
use const Grpc\STATUS_UNAUTHENTICATED;

class AuthController extends BaseGrpcService
{
public function __construct(
protected readonly UserService $userService,
protected readonly GrpcAuthMiddleware $authMiddleware
)
{
}

/**
* @throws Exception
*/
public function signup(FlexibleRequest $request, FlexibleResponse $response): FlexibleResponse
{
// Extract data from Any payload
$userData = $this->getRequestData($request);
$rules = new UserCreateRequest()->rules();
$validator = Validator::make($userData, $rules);
if ($validator->fails()) {
return $this->createErrorResponse(
$response,
Response::HTTP_UNPROCESSABLE_ENTITY,
STATUS_INVALID_ARGUMENT,
$validator->errors(),
errorMsg: ['message' => 'Validation errors']
);
}

// Pack response
$responseData = new Any();
try {
$user = $this->userService->create($userData);
$userResource = new UserResource($user);
$responseData->setValue(json_encode([
'data' => $userResource
]));
$response->setSuccess(true);
$response->setStatusCode(201);
$response->setData($responseData);
} catch (Exception $e) {
Log::error('User creation error', [
'error' => $e->getMessage(),
'trace' => $e->getTrace()
]);
throw $e; // Re-throw to be caught by the outer try-catch
}


return $response;
}

public function login(FlexibleRequest $in, FlexibleResponse $response): FlexibleResponse
{
$data = $this->getRequestData($in);
if (empty($data)) {
Log::error('Login request data is empty');
return $this->createErrorResponse(
$response,
Response::HTTP_BAD_REQUEST,
STATUS_INVALID_ARGUMENT,
'Request data is empty'
);
}
$rules = new UserReadRequest()->rules();
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
Log::error('User login validation error', [
'errors' => $validator->errors()
]);
self::getContext()->setStatus([
'code' => STATUS_INVALID_ARGUMENT,
'details' => $validator->errors()
]);
$response->setStatusCode(Response::HTTP_BAD_REQUEST);
$response->setError(new ErrorInfo([
'message' => $validator->errors()
]));
$response->setSuccess(false);
return $response;
// throw new Exception($validator->errors(), STATUS_INVALID_ARGUMENT);
}

$user = $this->userService->login($data);

if (!$user) {
self::getContext()->setStatus([
'code' => STATUS_UNAUTHENTICATED,
'details' => 'Invalid credentials'
]);
$response->setError(new ErrorInfo([
'message' => 'Invalid credentials'
]));
$response->setStatusCode(Response::HTTP_UNAUTHORIZED);
$response->setSuccess(false);
return $response;
// throw new Exception ('Invalid credentials', STATUS_UNAUTHENTICATED);
}

// Pack user data into Any
$userResource = new UserResource($user);
$anyData = new Any();
$anyData->setValue(json_encode([
'message' => 'Login successful',
'data' => $userResource
]));

$response->setSuccess(true);
$response->setStatusCode(200);
$response->setData($anyData);

return $response;
}

public function logout(FlexibleRequest $in, FlexibleResponse $response): FlexibleResponse
{
try {
$this->authMiddleware->revokeGrpcToken(BaseGrpcService::getContext()->clientMetadata());
return $this->createResponse(
[],
$response,
Response::HTTP_NO_CONTENT,
);
} catch (Exception $e) {
Log::error('Logout error', [
'error' => $e->getMessage(),
'trace' => $e->getTrace()
]);
return $this->createErrorResponse($response, Response::HTTP_UNAUTHORIZED, STATUS_UNAUTHENTICATED, $isLoggedOut['error'] ?? 'Unauthenticated');
}
}


public function validateToken(FlexibleRequest $request, FlexibleResponse $response): FlexibleResponse
{
try {
$user = auth()->user();
$user->withAccessToken(app(GrpcAuthMiddleware::class)->getToken(BaseGrpcService::getContext()->clientMetadata()));

$user = $this->userService->validateToken($user);
if (is_null($user['data'])) {
return $this->createErrorResponse(
$response,
Response::HTTP_UNAUTHORIZED,
STATUS_INVALID_ARGUMENT,
$user['message'],
);
}
return $this->createResponse([
'message' => $user['message'],
'data' => $user['data']
],
$response
);
} catch (Exception $e) {
Log::error('Token validation error', [
'error' => $e->getMessage(),
'trace' => $e->getTrace()
]);

return $this->createErrorResponse(
$response,
Response::HTTP_UNAUTHORIZED,
STATUS_INVALID_ARGUMENT,
'Token validation failed: ' . $e->getMessage()
);
}
}

}
75 changes: 75 additions & 0 deletions app/Grpc/Controllers/BaseGrpcService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace App\Grpc\Controllers;

use App\Grpc\ErrorInfo;
use App\Grpc\FlexibleRequest;
use App\Grpc\FlexibleResponse;
use Google\Protobuf\Any;
use Grpc\ServerContext;

class BaseGrpcService
{
private static ServerContext $context;

public static function getContext(): ServerContext
{
return self::$context;
}

public static function setContext(ServerContext $context): void
{
self::$context = $context;
}

/**
* @param FlexibleRequest $request
* @return mixed
*/
protected function getRequestData(FlexibleRequest $request): mixed
{
$jsonData = $request->getPayload()->getValue();
return json_decode($jsonData, true);
}


/**
* @param array $data
* @param FlexibleResponse $response
*/
protected function createResponse(array $data, FlexibleResponse $response, $code = 200): FlexibleResponse
{
$responseData = new Any();
$responseData->setValue(json_encode($data));
$response->setData($responseData);
$response->setSuccess(true);
$response->setStatusCode($code);
return $response;
}

/**
* Create error response
*/
public function createErrorResponse(FlexibleResponse $response, int $statusCode, $code, string $message, ?array $errorMsg = null): FlexibleResponse
{
BaseGrpcService::getContext()->setStatus([
'code' => $code,
'details' => $message
]);
$error = new ErrorInfo();
$error->setCode($code);
$error->setMessage($message);

if (!is_null($errorMsg)) {
$any = new Any();
$any->setValue(json_encode($errorMsg));
$response->setData($any);
}

$response->setSuccess(false);
$response->setStatusCode($statusCode);
$response->setError($error);

return $response;
}
}
Loading