Skip to content

Commit 1817383

Browse files
Boy132DaneEverittdanny6167MrSoulPenguin
authored
Add changes from upstream (#2293)
Co-authored-by: DaneEveritt <dane@daneeveritt.com> Co-authored-by: danny6167 <danielb@purpleflaghosting.com> Co-authored-by: MrSoulPenguin <28676680+MrSoulPenguin@users.noreply.github.com>
1 parent d39a0c4 commit 1817383

40 files changed

Lines changed: 667 additions & 155 deletions

app/Events/User/Deleting.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Events\User;
4+
5+
use App\Events\Event;
6+
use App\Models\User;
7+
use Illuminate\Queue\SerializesModels;
8+
9+
class Deleting extends Event
10+
{
11+
use SerializesModels;
12+
13+
/**
14+
* Create a new event instance.
15+
*/
16+
public function __construct(public User $user) {}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace App\Events\User;
4+
5+
use App\Models\User;
6+
use Illuminate\Foundation\Events\Dispatchable;
7+
8+
final class PasswordChanged
9+
{
10+
use Dispatchable;
11+
12+
public function __construct(public readonly User $user) {}
13+
}

app/Http/Controllers/Api/Client/AccountController.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@
1313
use Illuminate\Http\JsonResponse;
1414
use Illuminate\Http\Request;
1515
use Illuminate\Http\Response;
16+
use Illuminate\Support\Facades\RateLimiter;
17+
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
1618
use Throwable;
1719

1820
class AccountController extends ClientApiController
1921
{
22+
/**
23+
* The number of seconds that must elapse before the email change throttle resets.
24+
*/
25+
private const EMAIL_UPDATE_THROTTLE = 60 * 60 * 24;
26+
2027
/**
2128
* AccountController constructor.
2229
*/
@@ -63,10 +70,22 @@ public function updateUsername(UpdateUsernameRequest $request): JsonResponse
6370
*/
6471
public function updateEmail(UpdateEmailRequest $request): JsonResponse
6572
{
66-
$original = $request->user()->email;
67-
$this->updateService->handle($request->user(), $request->validated());
73+
$user = $request->user();
74+
75+
// Only allow a user to change their email three times in the span
76+
// of 24 hours. This prevents malicious users from trying to find
77+
// existing accounts in the system by constantly changing their email.
78+
if (RateLimiter::tooManyAttempts($key = "user:update-email:{$user->uuid}", 3)) {
79+
throw new TooManyRequestsHttpException(message: 'Your email address has been changed too many times today. Please try again later.');
80+
}
81+
82+
$original = $user->email;
83+
84+
if (mb_strtolower($original) !== mb_strtolower($request->validated('email'))) {
85+
RateLimiter::hit($key, self::EMAIL_UPDATE_THROTTLE);
86+
87+
$this->updateService->handle($user, $request->validated());
6888

69-
if ($original !== $request->input('email')) {
7089
Activity::event('user:account.email-changed')
7190
->property(['old' => $original, 'new' => $request->input('email')])
7291
->log();
@@ -85,7 +104,9 @@ public function updateEmail(UpdateEmailRequest $request): JsonResponse
85104
*/
86105
public function updatePassword(UpdatePasswordRequest $request): JsonResponse
87106
{
88-
$user = $this->updateService->handle($request->user(), $request->validated());
107+
$user = Activity::event('user:account.password-changed')->transaction(function () use ($request) {
108+
return $this->updateService->handle($request->user(), $request->validated());
109+
});
89110

90111
$guard = $this->manager->guard();
91112
// If you do not update the user in the session you'll end up working with a
@@ -98,8 +119,6 @@ public function updatePassword(UpdatePasswordRequest $request): JsonResponse
98119
$guard->logoutOtherDevices($request->input('password'));
99120
}
100121

101-
Activity::event('user:account.password-changed')->log();
102-
103122
return new JsonResponse([], Response::HTTP_NO_CONTENT);
104123
}
105124
}

app/Http/Controllers/Api/Client/Servers/BackupController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public function store(StoreBackupRequest $request, Server $server): array
8787
}
8888

8989
$backup = Activity::event('server:backup.start')->transaction(function ($log) use ($action, $server, $request) {
90-
$server->backups()->lockForUpdate();
90+
$server->backups()->lockForUpdate()->count();
9191

9292
$backup = $action->handle($server, $request->input('name'));
9393

app/Http/Controllers/Api/Client/Servers/DatabaseController.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function index(GetDatabasesRequest $request, Server $server): array
6060
public function store(StoreDatabaseRequest $request, Server $server): array
6161
{
6262
$database = Activity::event('server:database.create')->transaction(function ($log) use ($request, $server) {
63-
$server->databases()->lockForUpdate();
63+
$server->databases()->lockForUpdate()->count();
6464

6565
$database = $this->deployDatabaseService->handle($server, $request->validated());
6666

@@ -87,15 +87,12 @@ public function store(StoreDatabaseRequest $request, Server $server): array
8787
*/
8888
public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array
8989
{
90-
$this->managementService->rotatePassword($database);
91-
$database->refresh();
92-
9390
Activity::event('server:database.rotate-password')
9491
->subject($database)
9592
->property('name', $database->database)
96-
->log();
93+
->transaction(fn () => $this->managementService->rotatePassword($database));
9794

98-
return $this->fractal->item($database)
95+
return $this->fractal->item($database->refresh())
9996
->parseIncludes(['password'])
10097
->transformWith($this->getTransformer(DatabaseTransformer::class))
10198
->toArray();

app/Http/Controllers/Api/Client/Servers/StartupController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ public function update(UpdateStartupVariableRequest $request, Server $server): a
8787

8888
$startup = $this->startupCommandService->handle($server);
8989

90-
if ($variable->env_variable !== $request->input('value')) {
90+
if ($original !== $request->input('value')) {
9191
Activity::event('server:startup.edit')
9292
->subject($variable)
9393
->property([
9494
'variable' => $variable->env_variable,
9595
'old' => $original,
96-
'new' => $request->input('value'),
96+
'new' => $request->input('value') ?? '',
9797
])
9898
->log();
9999
}

app/Http/Controllers/Api/Remote/Backups/BackupRemoteUploadController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function __invoke(Request $request, string $backup): JsonResponse
5656
/** @var Server $server */
5757
$server = $model->server;
5858
if ($server->node_id !== $node->id) {
59-
throw new HttpForbiddenException('You do not have permission to access that backup.');
59+
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
6060
}
6161

6262
// Prevent backups that have already been completed from trying to

app/Http/Controllers/Api/Remote/Backups/BackupStatusController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function index(ReportBackupCompleteRequest $request, string $backup): Jso
4747
/** @var Server $server */
4848
$server = $model->server;
4949
if ($server->node_id !== $node->id) {
50-
throw new HttpForbiddenException('You do not have permission to access that backup.');
50+
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
5151
}
5252

5353
if ($model->is_successful) {
@@ -97,6 +97,11 @@ public function restore(Request $request, string $backup): JsonResponse
9797
/** @var Backup $model */
9898
$model = Backup::query()->where('uuid', $backup)->firstOrFail();
9999

100+
$node = $request->attributes->get('node');
101+
if (!$model->server->node->is($node)) {
102+
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
103+
}
104+
100105
$model->server->update(['status' => null]);
101106

102107
Activity::event($request->boolean('successful') ? 'server:backup.restore-complete' : 'server.backup.restore-failed')

app/Http/Controllers/Api/Remote/Servers/ServerContainersController.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@
33
namespace App\Http\Controllers\Api\Remote\Servers;
44

55
use App\Enums\ContainerStatus;
6+
use App\Exceptions\Http\HttpForbiddenException;
67
use App\Http\Controllers\Controller;
7-
use App\Http\Requests\Api\Remote\ServerRequest;
88
use App\Models\Server;
99
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Http\Request;
1011

1112
class ServerContainersController extends Controller
1213
{
1314
/**
1415
* Updates the server container's status on the Panel
1516
*/
16-
public function status(ServerRequest $request, Server $server): JsonResponse
17+
public function status(Request $request, Server $server): JsonResponse
1718
{
19+
if (!$server->node->is($request->attributes->get('node'))) {
20+
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
21+
}
22+
1823
$status = ContainerStatus::tryFrom($request->json('data.new_state')) ?? ContainerStatus::Missing;
1924

2025
cache()->put("servers.$server->uuid.status", $status, now()->addHour());

app/Http/Controllers/Api/Remote/Servers/ServerDetailsController.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
namespace App\Http\Controllers\Api\Remote\Servers;
44

55
use App\Enums\ServerState;
6+
use App\Exceptions\Http\HttpForbiddenException;
67
use App\Facades\Activity;
78
use App\Http\Controllers\Controller;
8-
use App\Http\Requests\Api\Remote\ServerRequest;
99
use App\Http\Resources\Daemon\ServerConfigurationCollection;
1010
use App\Models\ActivityLog;
1111
use App\Models\Backup;
@@ -17,6 +17,7 @@
1717
use Illuminate\Http\JsonResponse;
1818
use Illuminate\Http\Request;
1919
use Throwable;
20+
use Webmozart\Assert\Assert;
2021

2122
class ServerDetailsController extends Controller
2223
{
@@ -33,8 +34,21 @@ public function __construct(
3334
* Returns details about the server that allows daemon to self-recover and ensure
3435
* that the state of the server matches the Panel at all times.
3536
*/
36-
public function __invoke(ServerRequest $request, Server $server): JsonResponse
37+
public function __invoke(Request $request, Server $server): JsonResponse
3738
{
39+
Assert::isInstanceOf($node = $request->attributes->get('node'), Node::class);
40+
41+
$transfer = $server->transfer;
42+
43+
// If the server is being transferred allow either node to request information about
44+
// the server. If the server is not being transferred only the target node is allowed
45+
// to fetch these details.
46+
$valid = $transfer ? $node->id === $transfer->old_node || $node->id === $transfer->new_node : $node->id === $server->node_id;
47+
48+
if (!$valid) {
49+
throw new HttpForbiddenException('Requesting node does not have permission to access this server.');
50+
}
51+
3852
return new JsonResponse([
3953
'settings' => $this->configurationStructureService->handle($server),
4054
'process_configuration' => $this->eggConfigurationService->handle($server),

0 commit comments

Comments
 (0)