Skip to content

Commit 0f9c4e7

Browse files
authored
Merge pull request #153 from fleetbase/dev-v1.6.13
v1.6.13 - Configurable throttle
2 parents 91d2c56 + fda7ca2 commit 0f9c4e7

File tree

10 files changed

+205
-65
lines changed

10 files changed

+205
-65
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fleetbase/core-api",
3-
"version": "1.6.12",
3+
"version": "1.6.13",
44
"description": "Core Framework and Resources for Fleetbase API",
55
"keywords": [
66
"fleetbase",
@@ -21,6 +21,7 @@
2121
"php": "^8.0",
2222
"aws/aws-sdk-php-laravel": "^3.7",
2323
"fleetbase/laravel-mysql-spatial": "^1.0.2",
24+
"fleetbase/countries": "^0.8.3",
2425
"fleetbase/twilio": "^5.0.1",
2526
"giggsey/libphonenumber-for-php": "^8.13",
2627
"google/apiclient": "^2.18",
@@ -44,7 +45,6 @@
4445
"maatwebsite/excel": "^3.1",
4546
"phpoffice/phpspreadsheet": "^1.28",
4647
"phrity/websocket": "^1.7",
47-
"pragmarx/countries": "^0.8.2",
4848
"sentry/sentry-laravel": "*",
4949
"spatie/laravel-activitylog": "^4.7",
5050
"spatie/laravel-google-cloud-storage": "^2.2",

config/api.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
<?php
22

3-
return [];
3+
return [
4+
'throttle' => [
5+
'max_attempts' => env('THROTTLE_REQUESTS_PER_MINUTE', 120),
6+
'decay_minutes' => env('THROTTLE_DECAY_MINUTES', 1),
7+
],
8+
];

src/Expansions/Route.php

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
use Closure;
66
use Fleetbase\Build\Expansion;
7+
use Fleetbase\Http\Controllers\Internal\v1\AuthController;
8+
use Fleetbase\Http\Middleware\ThrottleRequests;
79
use Fleetbase\Routing\RESTRegistrar;
810
use Illuminate\Routing\PendingResourceRegistration;
11+
use Illuminate\Routing\Router;
912
use Illuminate\Support\Str;
1013

1114
class Route implements Expansion
@@ -37,7 +40,7 @@ public function fleetbaseRestRoutes()
3740
* @return PendingResourceRegistration
3841
*/
3942
return function (string $name, $controller = null, $options = [], ?\Closure $callback = null) {
40-
/** @var \Illuminate\Routing\Router $this */
43+
/** @var Router $this */
4144
if (is_callable($controller) && $callback === null) {
4245
$callback = $controller;
4346
$controller = null;
@@ -65,7 +68,7 @@ public function fleetbaseRestRoutes()
6568
public function fleetbaseRoutes()
6669
{
6770
return function (string $name, callable|array|null $registerFn = null, $options = [], $controller = null) {
68-
/** @var \Illuminate\Routing\Router $this */
71+
/** @var Router $this */
6972
if (is_array($registerFn) && !empty($registerFn) && empty($options)) {
7073
$options = $registerFn;
7174
}
@@ -112,50 +115,44 @@ function ($router) use ($registerFn, $make, $controller) {
112115
};
113116
}
114117

115-
public function fleetbaseAuthRoutes()
118+
public function fleetbaseAuthRoutes(): \Closure
116119
{
117-
return function (?callable $registerFn = null, ?callable $registerProtectedFn = null) {
118-
/** @var \Illuminate\Routing\Router $this */
119-
return $this->group(
120-
['prefix' => 'auth'],
121-
function ($router) use ($registerFn, $registerProtectedFn) {
122-
$router->group(
123-
['middleware' => ['throttle:10,1', \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]],
124-
function ($router) use ($registerFn) {
125-
$router->post('login', 'AuthController@login');
126-
$router->post('sign-up', 'AuthController@signUp');
127-
$router->post('logout', 'AuthController@logout');
128-
$router->post('get-magic-reset-link', 'AuthController@createPasswordReset');
129-
$router->post('reset-password', 'AuthController@resetPassword');
130-
$router->post('create-verification-session', 'AuthController@createVerificationSession');
131-
$router->post('validate-verification-session', 'AuthController@validateVerificationSession');
132-
$router->post('send-verification-email', 'AuthController@sendVerificationEmail');
133-
$router->post('verify-email', 'AuthController@verifyEmail');
134-
$router->get('validate-verification', 'AuthController@validateVerificationCode');
135-
136-
if (is_callable($registerFn)) {
137-
$registerFn($router);
138-
}
139-
}
140-
);
141-
142-
$router->group(
143-
['middleware' => ['fleetbase.protected', \Spatie\ResponseCache\Middlewares\DoNotCacheResponse::class]],
144-
function ($router) use ($registerProtectedFn) {
145-
$router->post('switch-organization', 'AuthController@switchOrganization');
146-
$router->post('join-organization', 'AuthController@joinOrganization');
147-
$router->post('create-organization', 'AuthController@createOrganization');
148-
$router->get('session', 'AuthController@session');
149-
$router->get('organizations', 'AuthController@getUserOrganizations');
150-
$router->get('services', 'AuthController@services');
151-
152-
if (is_callable($registerProtectedFn)) {
153-
$registerProtectedFn($router);
154-
}
155-
}
156-
);
157-
}
158-
);
120+
return function (?string $authControllerClass = null, ?callable $registerFn = null, ?callable $registerProtectedFn = null) {
121+
$authControllerClass ??= AuthController::class;
122+
/** @var Router $this */
123+
$this->group(['prefix' => 'auth'], function (Router $router) use ($authControllerClass, $registerFn, $registerProtectedFn) {
124+
// Public auth routes with throttle
125+
$router->group(['middleware' => [ThrottleRequests::class]], function (Router $router) use ($authControllerClass, $registerFn) {
126+
$router->post('login', [$authControllerClass, 'login']);
127+
$router->post('sign-up', [$authControllerClass, 'signUp']);
128+
$router->post('logout', [$authControllerClass, 'logout']);
129+
$router->post('get-magic-reset-link', [$authControllerClass, 'createPasswordReset']);
130+
$router->post('reset-password', [$authControllerClass, 'resetPassword']);
131+
$router->post('create-verification-session', [$authControllerClass, 'createVerificationSession']);
132+
$router->post('validate-verification-session', [$authControllerClass, 'validateVerificationSession']);
133+
$router->post('send-verification-email', [$authControllerClass, 'sendVerificationEmail']);
134+
$router->post('verify-email', [$authControllerClass, 'verifyEmail']);
135+
$router->get('validate-verification', [$authControllerClass, 'validateVerificationCode']);
136+
137+
if (is_callable($registerFn)) {
138+
$registerFn($router);
139+
}
140+
});
141+
142+
// Protected auth routes
143+
$router->group(['middleware' => ['fleetbase.protected']], function (Router $router) use ($authControllerClass, $registerProtectedFn) {
144+
$router->post('switch-organization', [$authControllerClass, 'switchOrganization']);
145+
$router->post('join-organization', [$authControllerClass, 'joinOrganization']);
146+
$router->post('create-organization', [$authControllerClass, 'createOrganization']);
147+
$router->get('session', [$authControllerClass, 'session']);
148+
$router->get('organizations', [$authControllerClass, 'getUserOrganizations']);
149+
$router->get('services', [$authControllerClass, 'services']);
150+
151+
if (is_callable($registerProtectedFn)) {
152+
$registerProtectedFn($router);
153+
}
154+
});
155+
});
159156
};
160157
}
161158

src/Http/Middleware/AdminGuard.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Fleetbase\Http\Middleware;
4+
5+
use Fleetbase\Support\Auth;
6+
use Illuminate\Http\Request;
7+
8+
class AdminGuard
9+
{
10+
/**
11+
* Handle an incoming request.
12+
*/
13+
public function handle(Request $request, \Closure $next)
14+
{
15+
if (auth()->check()) {
16+
/** @var \Fleetbase\Models\User $user */
17+
$user = Auth::getUserFromSession($request);
18+
19+
if ($user->isAdmin()) {
20+
return $next($request);
21+
}
22+
}
23+
24+
return response()->error('User is not authorized to access this resource.', 401);
25+
}
26+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Fleetbase\Http\Middleware;
4+
5+
use Illuminate\Routing\Middleware\ThrottleRequests as ThrottleRequestsMiddleware;
6+
7+
class ThrottleRequests extends ThrottleRequestsMiddleware
8+
{
9+
public function handle($request, \Closure $next, $maxAttempts = null, $decayMinutes = null, $prefix = '')
10+
{
11+
$maxAttempts = config('api.throttle.max_attempts', 90);
12+
$decayMinutes = config('api.throttle.decay_minutes', 1);
13+
14+
return parent::handle($request, $next, $maxAttempts, $decayMinutes, $prefix);
15+
}
16+
}

src/Notifications/UserForgotPassword.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ class UserForgotPassword extends Notification implements ShouldQueue
2828
*
2929
* @return void
3030
*/
31-
public function __construct(?VerificationCode $verificationCode)
31+
public function __construct(?VerificationCode $verificationCode, ?string $url = null)
3232
{
3333
$this->verificationCode = $verificationCode;
34-
$this->url = Utils::consoleUrl('auth/reset-password/' . $verificationCode->uuid, ['code' => $verificationCode->code]);
34+
$this->url = $url ?? Utils::consoleUrl('auth/reset-password/' . $verificationCode->uuid, ['code' => $verificationCode->code]);
3535
}
3636

3737
/**

src/Providers/CoreServiceProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class CoreServiceProvider extends ServiceProvider
5757
\Fleetbase\Http\Middleware\TrackPresence::class,
5858
],
5959
'fleetbase.api' => [
60-
'throttle:80,1',
60+
\Fleetbase\Http\Middleware\ThrottleRequests::class,
6161
\Illuminate\Session\Middleware\StartSession::class,
6262
\Fleetbase\Http\Middleware\AuthenticateOnceWithBasicAuth::class,
6363
\Illuminate\Routing\Middleware\SubstituteBindings::class,

src/Support/CacheHelper.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Fleetbase\Support;
4+
5+
use Illuminate\Cache\RedisStore;
6+
use Illuminate\Support\Facades\Cache;
7+
use Illuminate\Support\Facades\Redis;
8+
9+
class CacheHelper extends Cache
10+
{
11+
/**
12+
* Delete cache keys matching a given pattern.
13+
*
14+
* Works only when the cache driver is Redis.
15+
* Uses SCAN for production safety instead of KEYS.
16+
*
17+
* @param string $pattern Pattern for matching keys (supports wildcards, e.g., "deployments:index:*").
18+
*
19+
* @return int number of keys deleted
20+
*/
21+
public static function forgetByPattern(string $pattern): int
22+
{
23+
// Only works for Redis
24+
if (Cache::getStore() instanceof RedisStore) {
25+
$deletedCount = 0;
26+
$cursor = 0;
27+
28+
do {
29+
[$cursor, $keys] = Redis::scan($cursor, [
30+
'match' => $pattern,
31+
'count' => 100,
32+
]);
33+
34+
if (!empty($keys)) {
35+
$deletedCount += Redis::del(...$keys);
36+
}
37+
} while ($cursor != 0);
38+
39+
return $deletedCount;
40+
}
41+
42+
// Fallback: throw error for unsupported drivers
43+
throw new \RuntimeException('forgetByPattern only works with Redis cache store.');
44+
}
45+
}

src/Traits/DisablesSoftDeletes.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Fleetbase\Traits;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Database\Eloquent\SoftDeletingScope;
7+
8+
trait DisablesSoftDeletes
9+
{
10+
/**
11+
* Override default delete behavior to always hard delete.
12+
*/
13+
protected function performDeleteOnModel()
14+
{
15+
$this->forceDelete();
16+
}
17+
18+
/**
19+
* Remove the SoftDeletes global scope during model boot.
20+
*/
21+
protected static function bootDisablesSoftDeletes()
22+
{
23+
static::addGlobalScope('disablesSoftDeletes', function (Builder $builder) {
24+
$builder->withoutGlobalScope(SoftDeletingScope::class);
25+
});
26+
}
27+
28+
/**
29+
* Disables soft delete scrop for all eloquent queries.
30+
*/
31+
public function newEloquentBuilder($query)
32+
{
33+
return parent::newEloquentBuilder($query)->withoutGlobalScope(SoftDeletingScope::class);
34+
}
35+
36+
/**
37+
* Prevent the model from reporting as trashed.
38+
*/
39+
public function trashed()
40+
{
41+
return false;
42+
}
43+
44+
/**
45+
* Prevent restore operations.
46+
*/
47+
public function restore()
48+
{
49+
return $this;
50+
}
51+
}

0 commit comments

Comments
 (0)