From 51226a83f97a15db05de07401332910b53dddc6e Mon Sep 17 00:00:00 2001 From: Juan24 <93536474+juanbroder24@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:15:44 -0300 Subject: [PATCH 1/3] Add application rules field and upsert API Add a JSON 'rules' column to applications (migration) and wire it into the Application model (fillable + array cast). Introduce UpsertApplicationRulesRequest to validate tenant_user_id and rules, and add ApplicationController::upsertRules to bulk-replace rules for all applications belonging to a tenant_user_id. Register a new POST route (applications/rules). Enhance ApiAuthMiddleware with debug logging, availability checks, and version-based access enforcement using application rules (canAccessRequestedVersion). --- .../Controllers/ApplicationController.php | 27 ++++++++ app/Http/Middleware/ApiAuthMiddleware.php | 66 +++++++++++++++---- .../UpsertApplicationRulesRequest.php | 32 +++++++++ app/Models/Application.php | 2 + ...000001_add_rules_to_applications_table.php | 33 ++++++++++ routes/api.php | 1 + 6 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 app/Http/Requests/UpsertApplicationRulesRequest.php create mode 100644 database/migrations/2026_03_04_000001_add_rules_to_applications_table.php diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php index 53df414..8fd64cb 100644 --- a/app/Http/Controllers/ApplicationController.php +++ b/app/Http/Controllers/ApplicationController.php @@ -4,6 +4,8 @@ use App\Classes\Repositories\ApplicationRepositoryInterface; use App\Classes\Transformers\ApplicationTransformer; +use App\Http\Requests\UpsertApplicationRulesRequest; +use App\Models\Application; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; @@ -494,4 +496,29 @@ public function deactivate($id) return response()->json($response->toArray(), 200); } + + /** + * Bulk replace rules for all applications belonging to a tenant user. + * + * @param UpsertApplicationRulesRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function upsertRules(UpsertApplicationRulesRequest $request) + { + $validated = $request->validated(); + + $updated = Application::query() + ->where('tenant_user_id', $validated['tenant_user_id']) + ->update([ + 'rules' => $validated['rules'], + 'updated_at' => now(), + ]); + + return response()->json([ + 'status' => 200, + 'message' => 'Application rules updated successfully.', + 'tenant_user_id' => $validated['tenant_user_id'], + 'updated_count' => $updated, + ], 200); + } } diff --git a/app/Http/Middleware/ApiAuthMiddleware.php b/app/Http/Middleware/ApiAuthMiddleware.php index 01ffeb7..9d8cabe 100644 --- a/app/Http/Middleware/ApiAuthMiddleware.php +++ b/app/Http/Middleware/ApiAuthMiddleware.php @@ -6,40 +6,71 @@ use App\Models\UsageLog; use Carbon\Carbon; use Closure; +use Illuminate\Support\Facades\Log; class ApiAuthMiddleware extends BasicAuthMiddleware { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ public function handle($request, Closure $next) { $apiKey = $request->header('x-api-key'); $authHeader = $request->header('Authorization'); $isBasicAuth = $authHeader && str_starts_with($authHeader, 'Basic '); - if (!$apiKey && !$isBasicAuth) { + Log::debug('ApiAuthMiddleware: incoming request', [ + 'path' => $request->path(), + 'method' => $request->method(), + 'has_api_key' => !empty($apiKey), + 'is_basic_auth' => (bool) $isBasicAuth, + ]); + + if (!$apiKey && !$isBasicAuth) { + Log::debug('ApiAuthMiddleware: authentication missing'); return response()->json(['error' => 'Authentication required. Provide API key or Basic auth'], 401); } if ($isBasicAuth) { + Log::debug('ApiAuthMiddleware: delegating to BasicAuth'); return parent::handle($request, $next); } if ($apiKey) { - $application = Application::where('key', '=', $apiKey)->first(); + $application = Application::query()->where('key', '=', $apiKey)->first(); if (!$application) { + Log::debug('ApiAuthMiddleware: invalid api key', [ + 'api_key_prefix' => substr($apiKey, 0, 6), + 'path' => $request->path(), + ]); return response()->json(['error' => 'Invalid API key'], 401); } - if (!$application->is_active) { - return response()->json(['error' => 'Application is inactive'], 403); + Log::debug('ApiAuthMiddleware: application resolved', [ + 'application_id' => $application->id, + 'tenant_user_id' => $application->tenant_user_id, + 'is_active' => (bool) $application->is_active, + 'is_trashed' => $application->trashed(), + 'rules' => (array) $application->rules, + ]); + + if ($application->trashed() || !$application->is_active) { + Log::debug('ApiAuthMiddleware: application unavailable', [ + 'application_id' => $application->id, + ]); + return response()->json(['error' => 'Application is unavailable'], 403); } + + $canAccess = $this->canAccessRequestedVersion($request->path(), (array) $application->rules); + + Log::debug('ApiAuthMiddleware: version access decision', [ + 'application_id' => $application->id, + 'path' => $request->path(), + 'can_access' => $canAccess, + ]); + + if (!$canAccess) { + return response()->json(['error' => 'Application is not allowed to access this API version'], 403); + } + $usageLog = new UsageLog; $usageLog->application_id = $application->id; $usageLog->method = $request->method(); @@ -55,4 +86,17 @@ public function handle($request, Closure $next) return $next($request); } + + private function canAccessRequestedVersion(string $path, array $rules): bool + { + if (strpos($path, 'v1/') === 0) { + return !empty($rules['can_access_legacy_whatnow']); + } + + if (strpos($path, 'v2/') === 0) { + return !empty($rules['can_access_preparedness_v2']); + } + + return true; + } } diff --git a/app/Http/Requests/UpsertApplicationRulesRequest.php b/app/Http/Requests/UpsertApplicationRulesRequest.php new file mode 100644 index 0000000..d74ca66 --- /dev/null +++ b/app/Http/Requests/UpsertApplicationRulesRequest.php @@ -0,0 +1,32 @@ + 'required|string|max:255', + 'rules' => 'required|array', + ]; + } +} + diff --git a/app/Models/Application.php b/app/Models/Application.php index 1339591..2170df9 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -31,6 +31,7 @@ class Application extends Model 'estimated_users_count', 'key', 'is_active', + 'rules', ]; protected $dates = ['deleted_at']; @@ -42,6 +43,7 @@ class Application extends Model */ protected $casts = [ 'is_active' => 'boolean', + 'rules' => 'array', ]; /** diff --git a/database/migrations/2026_03_04_000001_add_rules_to_applications_table.php b/database/migrations/2026_03_04_000001_add_rules_to_applications_table.php new file mode 100644 index 0000000..b4ff476 --- /dev/null +++ b/database/migrations/2026_03_04_000001_add_rules_to_applications_table.php @@ -0,0 +1,33 @@ +json('rules')->nullable()->after('is_active'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('rules'); + }); + } +} + diff --git a/routes/api.php b/routes/api.php index 5874aac..3594efa 100644 --- a/routes/api.php +++ b/routes/api.php @@ -58,6 +58,7 @@ // Route::delete('org/{code}/image', 'OrganisationController@deleteImageById'); // "Applications" endpoints + Route::post('applications/rules', 'ApplicationController@upsertRules'); Route::get('apps', 'ApplicationController@getAllForUser'); Route::post('apps', 'ApplicationController@create'); Route::get('apps/{id}', 'ApplicationController@getById'); From 355adbd2e77c4b338f50130f549d815af4b50524 Mon Sep 17 00:00:00 2001 From: Juan24 <93536474+juanbroder24@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:25:07 -0300 Subject: [PATCH 2/3] Update ApiAuthMiddleware.php --- app/Http/Middleware/ApiAuthMiddleware.php | 30 +---------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/app/Http/Middleware/ApiAuthMiddleware.php b/app/Http/Middleware/ApiAuthMiddleware.php index 9d8cabe..f52d4df 100644 --- a/app/Http/Middleware/ApiAuthMiddleware.php +++ b/app/Http/Middleware/ApiAuthMiddleware.php @@ -16,20 +16,11 @@ public function handle($request, Closure $next) $authHeader = $request->header('Authorization'); $isBasicAuth = $authHeader && str_starts_with($authHeader, 'Basic '); - Log::debug('ApiAuthMiddleware: incoming request', [ - 'path' => $request->path(), - 'method' => $request->method(), - 'has_api_key' => !empty($apiKey), - 'is_basic_auth' => (bool) $isBasicAuth, - ]); - if (!$apiKey && !$isBasicAuth) { - Log::debug('ApiAuthMiddleware: authentication missing'); return response()->json(['error' => 'Authentication required. Provide API key or Basic auth'], 401); } if ($isBasicAuth) { - Log::debug('ApiAuthMiddleware: delegating to BasicAuth'); return parent::handle($request, $next); } @@ -37,36 +28,17 @@ public function handle($request, Closure $next) $application = Application::query()->where('key', '=', $apiKey)->first(); if (!$application) { - Log::debug('ApiAuthMiddleware: invalid api key', [ - 'api_key_prefix' => substr($apiKey, 0, 6), - 'path' => $request->path(), - ]); + return response()->json(['error' => 'Invalid API key'], 401); } - Log::debug('ApiAuthMiddleware: application resolved', [ - 'application_id' => $application->id, - 'tenant_user_id' => $application->tenant_user_id, - 'is_active' => (bool) $application->is_active, - 'is_trashed' => $application->trashed(), - 'rules' => (array) $application->rules, - ]); if ($application->trashed() || !$application->is_active) { - Log::debug('ApiAuthMiddleware: application unavailable', [ - 'application_id' => $application->id, - ]); return response()->json(['error' => 'Application is unavailable'], 403); } $canAccess = $this->canAccessRequestedVersion($request->path(), (array) $application->rules); - Log::debug('ApiAuthMiddleware: version access decision', [ - 'application_id' => $application->id, - 'path' => $request->path(), - 'can_access' => $canAccess, - ]); - if (!$canAccess) { return response()->json(['error' => 'Application is not allowed to access this API version'], 403); } From 0d10da36722d195b508d73dd14138dcac2ca888e Mon Sep 17 00:00:00 2001 From: Juan24 <93536474+juanbroder24@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:30:59 -0300 Subject: [PATCH 3/3] Update ApiAuthMiddleware.php --- app/Http/Middleware/ApiAuthMiddleware.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Middleware/ApiAuthMiddleware.php b/app/Http/Middleware/ApiAuthMiddleware.php index f52d4df..72cdc81 100644 --- a/app/Http/Middleware/ApiAuthMiddleware.php +++ b/app/Http/Middleware/ApiAuthMiddleware.php @@ -62,11 +62,11 @@ public function handle($request, Closure $next) private function canAccessRequestedVersion(string $path, array $rules): bool { if (strpos($path, 'v1/') === 0) { - return !empty($rules['can_access_legacy_whatnow']); + return $rules['can_access_legacy_whatnow']; } if (strpos($path, 'v2/') === 0) { - return !empty($rules['can_access_preparedness_v2']); + return $rules['can_access_preparedness_v2']; } return true;