Skip to content

Commit d8aa15b

Browse files
authored
Merge pull request #9 from exposedev/feature/cooldown
Add cooldown feature
2 parents ec3925f + 4a87c1f commit d8aa15b

File tree

13 files changed

+283
-6
lines changed

13 files changed

+283
-6
lines changed

app/Connections/ConnectionManager.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public function limitConnectionLength(ControlConnection $connection, int $maximu
5050

5151
$this->loop->addTimer($maximumConnectionLength * 60, function () use ($connection) {
5252
$connection->closeWithoutReconnect();
53+
54+
// Set cooldown for authenticated users
55+
$cooldownPeriod = config('expose-server.connection_cooldown_period', 0);
56+
if ($cooldownPeriod > 0 && !empty($connection->authToken)) {
57+
$cooldownEndsAt = time() + ($cooldownPeriod * 60);
58+
app(\Expose\Server\Contracts\UserRepository::class)->setCooldownForToken($connection->authToken, $cooldownEndsAt);
59+
$this->statisticsCollector->cooldownTriggered();
60+
}
5361
});
5462
}
5563

app/Contracts/StatisticsCollector.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public function portShared($authToken = null);
1010

1111
public function incomingRequest();
1212

13+
public function cooldownTriggered();
14+
1315
public function flush();
1416

1517
public function save();

app/Contracts/UserRepository.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ public function deleteUser($id): PromiseInterface;
2323
public function getUsersByTokens(array $authTokens): PromiseInterface;
2424

2525
public function updateLastSharedAt($id): PromiseInterface;
26+
27+
public function setCooldownForToken(string $authToken, int $cooldownEndsAt): PromiseInterface;
2628
}

app/Http/Controllers/Admin/StoreSettingsController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ public function handle(Request $request, ConnectionInterface $httpConnection)
2929

3030
config()->set('expose-server.maximum_connection_length', $request->get('maximum_connection_length'));
3131

32+
config()->set('expose-server.connection_cooldown_period', $request->get('connection_cooldown_period'));
33+
3234
config()->set('expose-server.messages.message_of_the_day', Arr::get($messages, 'message_of_the_day'));
3335

3436
config()->set('expose-server.messages.custom_subdomain_unauthorized', Arr::get($messages, 'custom_subdomain_unauthorized'));
3537

38+
config()->set('expose-server.messages.connection_cooldown_active', Arr::get($messages, 'connection_cooldown_active'));
39+
3640
$httpConnection->send(
3741
respond_json([
3842
'configuration' => $this->configuration,

app/Http/Controllers/ControlMessageController.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,16 @@ protected function authenticate(ConnectionInterface $connection, $data)
139139
} elseif ($data->type === 'tcp') {
140140
$this->handleTcpConnection($connection, $data, $user);
141141
}
142-
}, function () use ($connection) {
142+
}, function ($error) use ($connection) {
143+
$message = config('expose-server.messages.invalid_auth_token');
144+
if ($error instanceof \Exception) {
145+
$message = $error->getMessage();
146+
}
147+
143148
$connection->send(json_encode([
144149
'event' => 'authenticationFailed',
145150
'data' => [
146-
'message' => config('expose-server.messages.invalid_auth_token'),
151+
'message' => $message,
147152
],
148153
]));
149154
$connection->close();
@@ -287,8 +292,19 @@ protected function verifyAuthToken(ConnectionInterface $connection): PromiseInte
287292
->getUserByToken($authToken)
288293
->then(function ($user) use ($deferred) {
289294
if (is_null($user)) {
290-
$deferred->reject(new \Exception("Invalid auth token"));
295+
$deferred->reject(new \Exception(config('expose-server.messages.invalid_auth_token')));
291296
} else {
297+
// Check if user is in cooldown
298+
if (!empty($user['cooldown_ends_at'])) {
299+
$currentTime = time();
300+
if ($currentTime < $user['cooldown_ends_at']) {
301+
$minutesRemaining = ceil(($user['cooldown_ends_at'] - $currentTime) / 60);
302+
$message = str_replace(':minutes', $minutesRemaining, config('expose-server.messages.connection_cooldown_active'));
303+
$deferred->reject(new \Exception($message));
304+
return;
305+
}
306+
}
307+
292308
$this->userRepository
293309
->updateLastSharedAt($user['id'])
294310
->then(function () use ($deferred, $user) {

app/StatisticsCollector/DatabaseStatisticsCollector.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class DatabaseStatisticsCollector implements StatisticsCollector
1919
/** @var int */
2020
protected $requests = 0;
2121

22+
/** @var int */
23+
protected $cooldowns = 0;
24+
2225
public function __construct(DatabaseInterface $database)
2326
{
2427
$this->database = $database;
@@ -34,6 +37,7 @@ public function flush()
3437
$this->sharedPorts = [];
3538
$this->sharedSites = [];
3639
$this->requests = 0;
40+
$this->cooldowns = 0;
3741
}
3842

3943
public function siteShared($authToken = null)
@@ -71,6 +75,15 @@ public function incomingRequest()
7175
$this->requests++;
7276
}
7377

78+
public function cooldownTriggered()
79+
{
80+
if (! $this->shouldCollectStatistics()) {
81+
return;
82+
}
83+
84+
$this->cooldowns++;
85+
}
86+
7487
public function save()
7588
{
7689
$sharedSites = 0;
@@ -84,15 +97,16 @@ public function save()
8497
});
8598

8699
$this->database->query('
87-
INSERT INTO statistics (timestamp, shared_sites, shared_ports, unique_shared_sites, unique_shared_ports, incoming_requests)
88-
VALUES (:timestamp, :shared_sites, :shared_ports, :unique_shared_sites, :unique_shared_ports, :incoming_requests)
100+
INSERT INTO statistics (timestamp, shared_sites, shared_ports, unique_shared_sites, unique_shared_ports, incoming_requests, cooldown_count)
101+
VALUES (:timestamp, :shared_sites, :shared_ports, :unique_shared_sites, :unique_shared_ports, :incoming_requests, :cooldown_count)
89102
', [
90103
'timestamp' => today()->toDateString(),
91104
'shared_sites' => $sharedSites,
92105
'shared_ports' => $sharedPorts,
93106
'unique_shared_sites' => count($this->sharedSites),
94107
'unique_shared_ports' => count($this->sharedPorts),
95108
'incoming_requests' => $this->requests,
109+
'cooldown_count' => $this->cooldowns,
96110
])
97111
->then(function () {
98112
$this->flush();

app/StatisticsRepository/DatabaseStatisticsRepository.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public function getStatistics($from, $until): PromiseInterface
2929
SUM(shared_ports) as shared_ports,
3030
SUM(unique_shared_sites) as unique_shared_sites,
3131
SUM(unique_shared_ports) as unique_shared_ports,
32-
SUM(incoming_requests) as incoming_requests
32+
SUM(incoming_requests) as incoming_requests,
33+
SUM(cooldown_count) as cooldown_count
3334
FROM statistics
3435
WHERE
3536
`timestamp` >= "'.$from.'" AND `timestamp` <= "'.$until.'"')

app/UserRepository/DatabaseUserRepository.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ protected function getUserDetails(array $user)
106106
$user['sites'] = $user['auth_token'] !== '' ? $this->connectionManager->getConnectionsForAuthToken($user['auth_token']) : [];
107107
$user['tcp_connections'] = $user['auth_token'] !== '' ? $this->connectionManager->getTcpConnectionsForAuthToken($user['auth_token']) : [];
108108

109+
$user['is_in_cooldown'] = false;
110+
$user['cooldown_minutes_remaining'] = 0;
111+
112+
if (!empty($user['cooldown_ends_at'])) {
113+
$currentTime = time();
114+
if ($currentTime < $user['cooldown_ends_at']) {
115+
$user['is_in_cooldown'] = true;
116+
$user['cooldown_minutes_remaining'] = ceil(($user['cooldown_ends_at'] - $currentTime) / 60);
117+
}
118+
}
119+
109120
return $user;
110121
}
111122

@@ -233,4 +244,20 @@ public function getUsersByTokens(array $authTokens): PromiseInterface
233244

234245
return $deferred->promise();
235246
}
247+
248+
public function setCooldownForToken(string $authToken, int $cooldownEndsAt): PromiseInterface
249+
{
250+
$deferred = new Deferred();
251+
252+
$this->database
253+
->query('UPDATE users SET cooldown_ends_at = :cooldown_ends_at WHERE auth_token = :token', [
254+
'cooldown_ends_at' => $cooldownEndsAt,
255+
'token' => $authToken,
256+
])
257+
->then(function (Result $result) use ($deferred) {
258+
$deferred->resolve(null);
259+
});
260+
261+
return $deferred->promise();
262+
}
236263
}

config/expose-server.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@
5454
*/
5555
'maximum_connection_length' => 0,
5656

57+
/*
58+
|--------------------------------------------------------------------------
59+
| Connection Cooldown Period
60+
|--------------------------------------------------------------------------
61+
|
62+
| After a client is disconnected due to reaching the maximum connection
63+
| length, they must wait this many minutes before reconnecting.
64+
| Set to 0 to disable the cooldown period.
65+
|
66+
*/
67+
'connection_cooldown_period' => 0,
68+
5769
/*
5870
|--------------------------------------------------------------------------
5971
| Maximum number of open connections
@@ -182,6 +194,7 @@
182194
'custom_domain_unauthorized' => 'You are not allowed to use this custom domain. If you think this should work, double-check the server setting and try again.',
183195

184196
'maximum_connection_length_reached' => 'You have reached the maximum connection length for this server. Please upgrade to Expose Pro for unlimited connection length.',
197+
'connection_cooldown_active' => 'You\'ve used your free session for now. Please wait :cooldown minutes before reconnecting, or upgrade to Expose Pro → https://expose.dev/pro',
185198
],
186199

187200
'statistics' => [
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE users ADD COLUMN cooldown_ends_at INTEGER DEFAULT NULL;

0 commit comments

Comments
 (0)