Merge branch 'feature/primaya' into staging
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\JwtMiddleware;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Primaya\Http\Controllers\Api\AuthController;
|
||||
use Modules\Primaya\Http\Controllers\Api\MasterController;
|
||||
@@ -23,12 +24,12 @@ Route::prefix('v1')->group(function () {
|
||||
Route::prefix('primaya')->group(function () {
|
||||
|
||||
// LOGIN (pakai corporate key)
|
||||
Route::middleware(['corporate.key'])->group(function () {
|
||||
Route::post('login', [AuthController::class, 'loginJwt']);
|
||||
});
|
||||
// Route::middleware(['corporate.key'])->group(function () {
|
||||
// Route::post('login', [AuthController::class, 'loginJwt']);
|
||||
// });
|
||||
|
||||
// JWT Protected
|
||||
Route::middleware(['auth:corporate-api'])->group(function () {
|
||||
// JWT Protected key digenerate oleh client
|
||||
Route::middleware([JwtMiddleware::class])->group(function () {
|
||||
|
||||
Route::middleware(Authorization::class)->group(function () {
|
||||
Route::post('search-member', [MemberController::class, 'search']);
|
||||
|
||||
47
app/Http/Middleware/JwtMiddleware.php
Normal file
47
app/Http/Middleware/JwtMiddleware.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\ApiClient;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
use App\Services\AuthService;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class JwtMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = $request->bearerToken();
|
||||
if (!$token) {
|
||||
return response()->json(['message' => 'Unauthorized!'], 401);
|
||||
}
|
||||
|
||||
$decoded = AuthService::verifyClientToken($token);
|
||||
if (!$decoded) {
|
||||
return response()->json(['message' => 'Invalid Token'], 401);
|
||||
}
|
||||
|
||||
// Identify client by sub claim
|
||||
$clientId = $decoded->sub ?? null;
|
||||
if (!$clientId) {
|
||||
return response()->json(['message' => 'Invalid client in token'], 401);
|
||||
}
|
||||
$clients = config('api_clients.clients');;
|
||||
$client = collect($clients)->where('api_key', $clientId)->first();
|
||||
if (!$client || ($client->is_revoked ?? false)) {
|
||||
return response()->json(['message' => 'Client not found or revoked'], 401);
|
||||
}
|
||||
// Attach client info to request
|
||||
$request->attributes->set('client', $client);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
115
app/Services/AuthService.php
Normal file
115
app/Services/AuthService.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\RefreshToken;
|
||||
|
||||
class AuthService
|
||||
{
|
||||
/**
|
||||
* Generate RSA private/public key pair if missing
|
||||
*/
|
||||
public static function ensureKeysExist()
|
||||
{
|
||||
$privateKeyPath = env('JWT_PRIVATE_KEY_PATH', storage_path('keys/private.pem'));
|
||||
$publicKeyPath = env('JWT_PUBLIC_KEY_PATH', storage_path('keys/public.pem'));
|
||||
if (!file_exists($privateKeyPath) || !file_exists($publicKeyPath)) {
|
||||
@mkdir(dirname($privateKeyPath), 0770, true);
|
||||
@mkdir(dirname($publicKeyPath), 0770, true);
|
||||
$cmd = "openssl genpkey -algorithm RSA -out $privateKeyPath -pkeyopt rsa_keygen_bits:2048 && openssl rsa -pubout -in $privateKeyPath -out $publicKeyPath";
|
||||
exec($cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue JWT access token for the given client
|
||||
*/
|
||||
public static function issueTokenForClient($client, $audience = null)
|
||||
{
|
||||
$privateKeyPath = env('JWT_PRIVATE_KEY_PATH', storage_path('keys/private.pem'));
|
||||
if (!file_exists($privateKeyPath)) {
|
||||
throw new \Exception('JWT private key not found');
|
||||
}
|
||||
$privateKey = file_get_contents($privateKeyPath);
|
||||
$now = time();
|
||||
$ttl = (int)env('JWT_TTL', 3600);
|
||||
$ttl = (int)env('JWT_TTL', 3600 * 24 * 7);
|
||||
$exp = $now + $ttl;
|
||||
$aud = $audience ?: config('app.url');
|
||||
$payload = [
|
||||
'iss' => config('app.url'),
|
||||
'sub' => $client->client_id,
|
||||
'aud' => $aud,
|
||||
'iat' => $now,
|
||||
'exp' => $exp,
|
||||
'jti' => \Illuminate\Support\Str::uuid()->toString(),
|
||||
'scope' => $client->scopes,
|
||||
'client_db_id' => $client->id,
|
||||
];
|
||||
return \Firebase\JWT\JWT::encode($payload, $privateKey, 'RS256');
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue a refresh token for the client (random string, store in DB as needed)
|
||||
*/
|
||||
public static function issueRefreshToken($client)
|
||||
{
|
||||
return false;
|
||||
// $rawToken = \Illuminate\Support\Str::random(64);
|
||||
// $hashedToken = hash('sha256', $rawToken); // or use bcrypt if you prefer
|
||||
|
||||
// $refresh = RefreshToken::create([
|
||||
// 'client_id' => $client->id,
|
||||
// 'token' => $hashedToken,
|
||||
// 'expires_at' => now()->addDays(30),
|
||||
// 'revoked' => false,
|
||||
// ]);
|
||||
|
||||
// // Return the raw token to the client
|
||||
// return $rawToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT token and return decoded payload or false
|
||||
*/
|
||||
public static function verifyToken($token)
|
||||
{
|
||||
$publicKeyPath = env('JWT_PUBLIC_KEY_PATH', storage_path('keys/public.pem'));
|
||||
if (!file_exists($publicKeyPath)) {
|
||||
throw new \Exception('JWT public key not found');
|
||||
}
|
||||
$publicKey = file_get_contents($publicKeyPath);
|
||||
try {
|
||||
return \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($publicKey, 'RS256'));
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function verifyClientToken($token)
|
||||
{
|
||||
try {
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) {
|
||||
return false;
|
||||
}
|
||||
$payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')));
|
||||
$clientId = $payload->sub ?? null;
|
||||
|
||||
if (!$clientId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$clients = config('api_clients.clients');
|
||||
$client = collect($clients)->where('api_key', $clientId)->first();
|
||||
|
||||
if (!$client || !isset($client['api_secret'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($client['api_secret'], 'HS256'));
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
config/api_clients.php
Normal file
8
config/api_clients.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'clients' => [[
|
||||
'api_key' => env('LMS_API_KEY', 'lms'),
|
||||
'api_secret' => env('LMS_API_SECRET', '8f3d6c1a9e2b5f4d8c7a6b0e9d1f2a3c4b5e6f7d8a9c0b1e2d3f4a5b6c7d8e9f'),
|
||||
]]
|
||||
];
|
||||
Reference in New Issue
Block a user