Compare commits

...

28 Commits

Author SHA1 Message Date
Boy132
1d02365efe
Fix eslint warnings (#3814) 2021-12-19 09:50:18 -08:00
wingdings255
1564742606
Update egg-ark--survival-evolved.json (#3809) 2021-12-19 09:44:32 -08:00
Mrxbox98
66c56b0da8
CPU Graph change (#3804) 2021-12-12 10:19:06 -08:00
Mrxbox98
15619fb8e4
Fixes overlapping Two Factor Authentication Box (#3803) 2021-12-12 10:15:44 -08:00
Alex
4e6fe112b0
fix(forge): actually fix forge regex (#3801)
For whatever reason, stupid me rebased the wrong branch in previous PR #3783 and didn't notice it, which contained the old egg instead.

This one actually fixes the regex and includes more debugging steps for easier troubleshooting.

Easy to view diff: <https://www.diffchecker.com/3iJ9lVzH>
2021-12-12 10:14:25 -08:00
Mrxbox98
f04b87a37c
FireFox Font Fix (#3805) 2021-12-12 10:14:09 -08:00
hz-ad
928b060647
Include HostEZ in sponsors list (#3788)
Update to append HostEZ to the sponsors list
2021-12-06 11:00:29 -08:00
Alex
b8bf537737
cmd(setup): validate email input, closes #3175 (#3716) 2021-12-04 10:52:09 -08:00
Charles Morgan
0d5ff6afac
Add Pug / Jade to file editor, closes #3512 (#3514) 2021-12-04 10:51:52 -08:00
Alex
5cde059f21
docs(docker): display correct variable for certificates (#3723)
Docker image and compose file uses `LE_EMAIL` and not `LETSENCRYPT_EMAIL`

Co-authored-by: Matthew Penner <me@matthewp.io>
2021-12-04 10:51:15 -08:00
Alex
0db772a82b
eggs: update source install script (#3604)
Installing basic packages are unnecessary as they already exist in yolks installer image. This also gets rid of Debian 10 lib32gcc package not being found, since installer image is Debian 11.
2021-12-04 10:50:50 -08:00
Paul Vogel
dcbc1360a9
Improve test coverage for LocationController (#3779)
By adding tests for create, update, delete
2021-12-04 10:50:36 -08:00
Patrick R
622b939f00
Show ipv6 with correct in-url syntax (#3776) 2021-12-04 10:35:55 -08:00
Lukas Moucka
e8e2911a92
Change order of docker images in JavaVersionModalFeature (#3782)
This changes the order of the Docker images in JavaVersionModalFeature, and also sets the default state to Java 17. Previously it was Java 16, even though the first entry in the list was Java 8, that confused a lot of people
2021-12-04 10:35:39 -08:00
Boy132
96c3338e96
Add the MC 1.18 message to Java Version Modal (#3778) 2021-12-04 10:35:20 -08:00
Alex
49d5ef271d
ARM64 support for the Panel Docker image, closes #3580 (#3709)
Co-authored-by: Dane Everitt <dane@daneeveritt.com>
2021-12-04 10:33:42 -08:00
Boy132
4cc8658334
GSL Token Modal Feature (#3746) 2021-12-04 10:29:24 -08:00
Yusta
a6e0e5dbda
Add app_url for mail sender (#3753)
Co-authored-by: Dane Everitt <dane@daneeveritt.com>
2021-12-04 10:26:00 -08:00
Desjardins Jérôme
10aaf00e83
use DB_PORT for mysql database connection (#3762)
DB_PORT is the env variable defined for the mysql port into Panel Configuration.
2021-12-04 10:25:02 -08:00
Paul Vogel
b9d73afb63
Fix typo in messsage when deleting a database (#3777) 2021-12-04 10:24:06 -08:00
Alex
59d47e746b
fix: Forge version regex for 1.17+ JPMS (#3783)
For 1.17 JPMS arguments, fix regex to match`^1\.(17|18|19|20|21|22|23)` or latest instead of only dot . minor versions, which is something I didn't notice in a previous PR. This should future proof it.

Changes Java image display order defaulting to 17, which the 1.17+ requires for unix args.
2021-12-04 10:23:37 -08:00
Alex
01e7a45cc5
fix(eggs): Forge latest version fetching (#3770)
Fixes a typo in fetching the latest versions. It was overwritten to "recommended" by mistake.

Easy to read diff: <https://www.diffchecker.com/U04gJTRu>
2021-11-29 10:14:08 -08:00
Dane Everitt
30bb629bad
Update CHANGELOG.md 2021-11-16 20:36:53 -08:00
Dane Everitt
bf9cbe2c6d
Add consistent CSRF token verification to API endpoints; address security concern with non-CSRF protected endpoints 2021-11-16 20:02:18 -08:00
Matthew Penner
cc31a0a6d0
tests(integration): don't expect non-required fields 2021-11-15 11:29:22 -07:00
Alex
01871d8a6c
add Java 17 LTS image to Minecraft eggs (#3744)
* feat: add Java 17 LTS for Minecraft

* feat: add java 17 option to java modal
2021-11-15 08:15:27 -08:00
Dane Everitt
17c03e9a4d
Fix broken session management for application api 2021-11-03 21:33:21 -07:00
Dane Everitt
e8a8405899
Remove tests 2021-11-03 21:22:14 -07:00
44 changed files with 361 additions and 376 deletions

View File

@ -8,6 +8,7 @@ APP_DELETE_MINUTES=10
APP_ENVIRONMENT_ONLY=true
LOG_CHANNEL=daily
APP_LOCALE=en
APP_URL=http://panel.example.com
DB_HOST=127.0.0.1
DB_PORT=3306
@ -30,7 +31,7 @@ MAILGUN_ENDPOINT=api.mailgun.net
# mail servers such as Gmail to reject your mail.
#
# @see: https://github.com/pterodactyl/panel/pull/3110
# SERVER_NAME=panel.yourdomain.com
# SERVER_NAME=panel.example.com
QUEUE_HIGH=high
QUEUE_STANDARD=standard

View File

@ -33,7 +33,7 @@ Note: If your `APP_URL` starts with `https://` you need to provide an `LETSENCRY
| ------------------- | ------------------------------------------------------------------------------ | -------- |
| `APP_URL` | The URL the panel will be reachable with (including protocol) | yes |
| `APP_TIMEZONE` | The timezone to use for the panel | yes |
| `LETSENCRYPT_EMAIL` | The email used for letsencrypt certificate generation | yes |
| `LE_EMAIL` | The email used for letsencrypt certificate generation | yes |
| `DB_HOST` | The host of the mysql instance | yes |
| `DB_PORT` | The port of the mysql instance | yes |
| `DB_DATABASE` | The name of the mysql database | yes |

View File

@ -57,7 +57,7 @@ fi
## check for DB up before starting the panel
echo "Checking database status."
until nc -z -v -w30 $DB_HOST 3306
until nc -z -v -w30 $DB_HOST $DB_PORT
do
echo "Waiting for database connection..."
# wait for 1 seconds before check again

View File

@ -36,6 +36,7 @@ jobs:
if: "!contains(github.ref, 'develop')"
with:
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
- name: Release Development Build
@ -43,5 +44,6 @@ jobs:
if: "contains(github.ref, 'develop')"
with:
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@ -3,7 +3,20 @@ This file is a running track of new features and fixes to each version of the pa
This project follows [Semantic Versioning](http://semver.org) guidelines.
## v1.6.6
### Fixed
* **[security]** Fixes a CSRF vulnerability for both the administrative test email endpoint and node auto-deployment token generation endpoint. [GHSA-wwgq-9jhf-qgw6](https://github.com/pterodactyl/panel/security/advisories/GHSA-wwgq-9jhf-qgw6)
### Changed
* Updates Minecraft eggs to include latest Java 17 yolk by default.
## v1.6.5
### Fixed
* Fixes broken application API endpoints due to changes introduced with session management in 1.6.4.
## v1.6.4
_This release should not be used, please use `1.6.5`. It has been pulled from our releases._
### Fixed
* Fixes a session management bug that would cause a user who signs out of one browser to be unintentionally logged out of other browser sessions when using the client API.

View File

@ -2,7 +2,7 @@
# Build the assets that are needed for the frontend. This build stage is then discarded
# since we won't need NodeJS anymore in the future. This Docker image ships a final production
# level distribution of Pterodactyl.
FROM mhart/alpine-node:14
FROM --platform=$TARGETOS/$TARGETARCH mhart/alpine-node:14
WORKDIR /app
COPY . ./
RUN yarn install --frozen-lockfile \
@ -10,7 +10,7 @@ RUN yarn install --frozen-lockfile \
# Stage 1:
# Build the actual container with all of the needed PHP dependencies that will run the application.
FROM php:7.4-fpm-alpine
FROM --platform=$TARGETOS/$TARGETARCH php:7.4-fpm-alpine
WORKDIR /app
COPY . ./
COPY --from=0 /app/public/assets ./public/assets

View File

@ -37,6 +37,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.|
| [**RocketNode**](https://rocketnode.net) | RocketNode is a VPS and Game Server provider that offers the best performing VPS and Game hosting Solutions at affordable prices! |
| [**HostEZ**](https://hostez.io) | Providing North America Valheim, Minecraft and other popular games with low latency, high uptime and maximum availability. EZ! |
## Documentation
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)

View File

@ -12,6 +12,7 @@ namespace Pterodactyl\Console\Commands\Environment;
use DateTimeZone;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Validation\Factory as ValidatorFactory;
use Pterodactyl\Traits\Commands\EnvironmentWriterTrait;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
@ -78,12 +79,13 @@ class AppSettingsCommand extends Command
/**
* AppSettingsCommand constructor.
*/
public function __construct(ConfigRepository $config, Kernel $command)
public function __construct(ConfigRepository $config, Kernel $command, ValidatorFactory $validator)
{
parent::__construct();
$this->command = $command;
$this->config = $config;
$this->command = $command;
$this->validator = $validator;
}
/**
@ -103,6 +105,18 @@ class AppSettingsCommand extends Command
$this->config->get('pterodactyl.service.author', 'unknown@unknown.com')
);
$validator = $this->validator->make(
['email' => $this->variables['APP_SERVICE_AUTHOR']],
['email' => 'email']
);
if ($validator->fails()) {
foreach ($validator->errors()->all() as $error) {
$this->output->error($error);
}
return 1;
}
$this->output->comment(trans('command/messages.environment.app.app_url_help'));
$this->variables['APP_URL'] = $this->option('url') ?? $this->ask(
trans('command/messages.environment.app.app_url'),

View File

@ -70,9 +70,12 @@ class Kernel extends HttpKernel
'api' => [
HandleStatelessRequest::class,
IsValidJson::class,
StartSession::class,
AuthenticateSession::class,
ApiSubstituteBindings::class,
'api..key:' . ApiKey::TYPE_APPLICATION,
AuthenticateApplicationUser::class,
VerifyCsrfToken::class,
AuthenticateIPAccess::class,
],
'client-api' => [
@ -83,6 +86,7 @@ class Kernel extends HttpKernel
SubstituteClientApiBindings::class,
'api..key:' . ApiKey::TYPE_ACCOUNT,
AuthenticateIPAccess::class,
VerifyCsrfToken::class,
// This is perhaps a little backwards with the Client API, but logically you'd be unable
// to create/get an API key without first enabling 2FA on the account, so I suppose in the
// end it makes sense.

View File

@ -8,6 +8,7 @@ use Illuminate\Http\Request;
use Pterodactyl\Models\User;
use Pterodactyl\Models\ApiKey;
use Illuminate\Auth\AuthManager;
use Illuminate\Support\Facades\Session;
use Illuminate\Contracts\Encryption\Encrypter;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
@ -55,7 +56,7 @@ class AuthenticateKey
public function handle(Request $request, Closure $next, int $keyType)
{
if (is_null($request->bearerToken()) && is_null($request->user())) {
throw new HttpException(401, null, null, ['WWW-Authenticate' => 'Bearer']);
throw new HttpException(401, 'A bearer token or valid user session cookie must be provided to access this endpoint.', null, ['WWW-Authenticate' => 'Bearer']);
}
// This is a request coming through using cookies, we have an authenticated user

View File

@ -2,18 +2,45 @@
namespace Pterodactyl\Http\Middleware;
use Closure;
use Pterodactyl\Models\ApiKey;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
* The URIs that should be excluded from CSRF verification. These are
* never hit by the front-end, and require specific token validation
* to work.
*
* @var array
* @var string[]
*/
protected $except = [
'remote/*',
'daemon/*',
'api/*',
];
protected $except = ['remote/*', 'daemon/*'];
/**
* Manually apply CSRF protection to routes depending on the authentication
* mechanism being used. If the API request is using an API key that exists
* in the database we can safely ignore CSRF protections, since that would be
* a manually initiated request by a user or server.
*
* All other requests should go through the standard CSRF protections that
* Laravel affords us. This code will be removed in v2 since we have switched
* to using Sanctum for the API endpoints, which handles that for us automatically.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Illuminate\Session\TokenMismatchException
*/
public function handle($request, Closure $next)
{
$key = $request->attributes->get('api_key');
if ($key instanceof ApiKey && $key->exists) {
return $next($request);
}
return parent::handle($request, $next);
}
}

View File

@ -4,7 +4,7 @@
"version": "PTDL_v1",
"update_url": null
},
"exported_at": "2021-07-04T19:18:34-04:00",
"exported_at": "2021-11-14T19:23:12+00:00",
"name": "Bungeecord",
"author": "support@pterodactyl.io",
"description": "For a long time, Minecraft server owners have had a dream that encompasses a free, easy, and reliable way to connect multiple Minecraft servers together. BungeeCord is the answer to said dream. Whether you are a small server wishing to string multiple game-modes together, or the owner of the ShotBow Network, BungeeCord is the ideal solution for you. With the help of BungeeCord, you will be able to unlock your community's full potential.",
@ -15,14 +15,15 @@
"images": [
"ghcr.io\/pterodactyl\/yolks:java_8",
"ghcr.io\/pterodactyl\/yolks:java_11",
"ghcr.io\/pterodactyl\/yolks:java_16"
"ghcr.io\/pterodactyl\/yolks:java_16",
"ghcr.io\/pterodactyl\/yolks:java_17"
],
"file_denylist": [],
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
"config": {
"files": "{\r\n \"config.yml\": {\r\n \"parser\": \"yaml\",\r\n \"find\": {\r\n \"listeners[0].query_port\": \"{{server.build.default.port}}\",\r\n \"listeners[0].host\": \"0.0.0.0:{{server.build.default.port}}\",\r\n \"servers.*.address\": {\r\n \"regex:^(127\\\\.0\\\\.0\\\\.1|localhost)(:\\\\d{1,5})?$\": \"{{config.docker.interface}}$2\"\r\n }\r\n }\r\n }\r\n}",
"startup": "{\r\n \"done\": \"Listening on \"\r\n}",
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"proxy.log.0\"\r\n}",
"logs": "{}",
"stop": "end"
},
"scripts": {

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
"version": "PTDL_v1",
"update_url": null
},
"exported_at": "2021-10-22T19:19:11+02:00",
"exported_at": "2021-11-14T19:21:07+00:00",
"name": "Paper",
"author": "parker@pterodactyl.io",
"description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.",
@ -15,7 +15,8 @@
"images": [
"ghcr.io\/pterodactyl\/yolks:java_8",
"ghcr.io\/pterodactyl\/yolks:java_11",
"ghcr.io\/pterodactyl\/yolks:java_16"
"ghcr.io\/pterodactyl\/yolks:java_16",
"ghcr.io\/pterodactyl\/yolks:java_17"
],
"file_denylist": [],
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",

View File

@ -4,7 +4,7 @@
"version": "PTDL_v1",
"update_url": null
},
"exported_at": "2021-10-22T19:19:23+02:00",
"exported_at": "2021-11-14T19:18:30+00:00",
"name": "Vanilla Minecraft",
"author": "support@pterodactyl.io",
"description": "Minecraft is a game about placing blocks and going on adventures. Explore randomly generated worlds and build amazing things from the simplest of homes to the grandest of castles. Play in Creative Mode with unlimited resources or mine deep in Survival Mode, crafting weapons and armor to fend off dangerous mobs. Do all this alone or with friends.",
@ -15,14 +15,15 @@
"images": [
"ghcr.io\/pterodactyl\/yolks:java_8",
"ghcr.io\/pterodactyl\/yolks:java_11",
"ghcr.io\/pterodactyl\/yolks:java_16"
"ghcr.io\/pterodactyl\/yolks:java_16",
"ghcr.io\/pterodactyl\/yolks:java_17"
],
"file_denylist": [],
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
"config": {
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
"startup": "{\r\n \"done\": \")! For help, type \"\r\n}",
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}",
"logs": "{}",
"stop": "stop"
},
"scripts": {

View File

@ -48,7 +48,7 @@
},
{
"name": "Server Map",
"description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles, Gen2",
"description": "Available Maps: TheIsland, TheCenter, Ragnarok, ScorchedEarth_P, Aberration_P, Extinction, Valguero_P, Genesis, CrystalIsles, Gen2, LostIsland",
"env_variable": "SERVER_MAP",
"default_value": "TheIsland",
"user_viewable": true,
@ -119,4 +119,4 @@
"rules": "nullable|string"
}
]
}
}

View File

@ -4,11 +4,13 @@
"version": "PTDL_v1",
"update_url": null
},
"exported_at": "2021-06-05T16:19:30-04:00",
"exported_at": "2021-09-10T14:36:37-04:00",
"name": "Counter-Strike: Global Offensive",
"author": "support@pterodactyl.io",
"description": "Counter-Strike: Global Offensive is a multiplayer first-person shooter video game developed by Hidden Path Entertainment and Valve Corporation.",
"features": null,
"features": [
"gsl_token"
],
"images": [
"ghcr.io\/pterodactyl\/games:source"
],
@ -16,19 +18,18 @@
"startup": ".\/srcds_run -game csgo -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}}",
"config": {
"files": "{}",
"startup": "{\r\n \"done\": \"Connection to Steam servers successful\",\r\n \"userInteraction\": []\r\n}",
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}",
"startup": "{\r\n \"done\": \"Connection to Steam servers successful\"\r\n}",
"logs": "{}",
"stop": "quit"
},
"scripts": {
"installation": {
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'ubuntu:18.04'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
"container": "ghcr.io\/pterodactyl\/installers:debian",
"entrypoint": "bash"
}
},
"variables": [
{
"variables": [{
"name": "Map",
"description": "The default map for the server.",
"env_variable": "SRCDS_MAP",

View File

@ -4,7 +4,7 @@
"version": "PTDL_v1",
"update_url": null
},
"exported_at": "2021-06-05T16:24:05-04:00",
"exported_at": "2021-09-10T14:36:22-04:00",
"name": "Custom Source Engine Game",
"author": "support@pterodactyl.io",
"description": "This option allows modifying the startup arguments and other details to run a custom SRCDS based game on the panel.",
@ -16,19 +16,18 @@
"startup": ".\/srcds_run -game {{SRCDS_GAME}} -console -port {{SERVER_PORT}} +map {{SRCDS_MAP}} +ip 0.0.0.0 -strictportbind -norestart",
"config": {
"files": "{}",
"startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}",
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs\/latest.log\"\r\n}",
"startup": "{\r\n \"done\": \"gameserver Steam ID\"\r\n}",
"logs": "{}",
"stop": "quit"
},
"scripts": {
"installation": {
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n##\r\n#\r\n# Variables\r\n# STEAM_USER, STEAM_PASS, STEAM_AUTH - Steam user setup. If a user has 2fa enabled it will most likely fail due to timeout. Leave blank for anon install.\r\n# WINDOWS_INSTALL - if it's a windows server you want to install set to 1\r\n# SRCDS_APPID - steam app id ffound here - https:\/\/developer.valvesoftware.com\/wiki\/Dedicated_Servers_List\r\n# EXTRA_FLAGS - when a server has extra glas for things like beta installs or updates.\r\n#\r\n##\r\n\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so",
"container": "ghcr.io\/pterodactyl\/installers:debian",
"entrypoint": "bash"
}
},
"variables": [
{
"variables": [{
"name": "Game ID",
"description": "The ID corresponding to the game to download and run using SRCDS.",
"env_variable": "SRCDS_APPID",
@ -83,4 +82,4 @@
"rules": "nullable|string"
}
]
}
}

View File

@ -4,11 +4,13 @@
"version": "PTDL_v1",
"update_url": null
},
"exported_at": "2021-08-27T00:12:31-04:00",
"exported_at": "2021-12-04T18:47:10+00:00",
"name": "Garrys Mod",
"author": "support@pterodactyl.io",
"description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.",
"features": null,
"features": [
"gsl_token"
],
"images": [
"ghcr.io\/pterodactyl\/games:source"
],
@ -22,7 +24,7 @@
},
"scripts": {
"installation": {
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n# Image to install with is 'debian:buster-slim'\r\napt -y update\r\napt -y --no-install-recommends install curl lib32gcc1 ca-certificates\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\nsv_downloadurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
"script": "#!\/bin\/bash\r\n# steamcmd Base Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\n\r\n## just in case someone removed the defaults.\r\nif [ \"${STEAM_USER}\" == \"\" ]; then\r\n echo -e \"steam user is not set.\\n\"\r\n echo -e \"Using anonymous user.\\n\"\r\n STEAM_USER=anonymous\r\n STEAM_PASS=\"\"\r\n STEAM_AUTH=\"\"\r\nelse\r\n echo -e \"user set to ${STEAM_USER}\"\r\nfi\r\n\r\n## download and install steamcmd\r\ncd \/tmp\r\nmkdir -p \/mnt\/server\/steamcmd\r\ncurl -sSL -o steamcmd.tar.gz https:\/\/steamcdn-a.akamaihd.net\/client\/installer\/steamcmd_linux.tar.gz\r\ntar -xzvf steamcmd.tar.gz -C \/mnt\/server\/steamcmd\r\nmkdir -p \/mnt\/server\/steamapps # Fix steamcmd disk write error when this folder is missing\r\ncd \/mnt\/server\/steamcmd\r\n\r\n# SteamCMD fails otherwise for some reason, even running as root.\r\n# This is changed at the end of the install process anyways.\r\nchown -R root:root \/mnt\r\nexport HOME=\/mnt\/server\r\n\r\n## install game using steamcmd\r\n.\/steamcmd.sh +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} $( [[ \"${WINDOWS_INSTALL}\" == \"1\" ]] && printf %s '+@sSteamCmdForcePlatformType windows' ) +force_install_dir \/mnt\/server +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit ## other flags may be needed depending on install. looking at you cs 1.6\r\n\r\n## set up 32 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk32\r\ncp -v linux32\/steamclient.so ..\/.steam\/sdk32\/steamclient.so\r\n\r\n## set up 64 bit libraries\r\nmkdir -p \/mnt\/server\/.steam\/sdk64\r\ncp -v linux64\/steamclient.so ..\/.steam\/sdk64\/steamclient.so\r\n\r\n# Creating needed default files for the game\r\ncd \/mnt\/server\/garrysmod\/lua\/autorun\/server\r\necho '\r\n-- Docs: https:\/\/wiki.garrysmod.com\/page\/resource\/AddWorkshop\r\n-- Place the ID of the workshop addon you want to be downloaded to people who join your server, not the collection ID\r\n-- Use https:\/\/beta.configcreator.com\/create\/gmod\/resources.lua to easily create a list based on your collection ID\r\n\r\nresource.AddWorkshop( \"\" )\r\n' > workshop.lua\r\n\r\ncd \/mnt\/server\/garrysmod\/cfg\r\necho '\r\n\/\/ Please do not set RCon in here, use the startup parameters.\r\n\r\nhostname\t\t\"New Gmod Server\"\r\nsv_password\t\t\"\"\r\nsv_loadingurl \"\"\r\nsv_downloadurl \"\"\r\n\r\n\/\/ Steam Server List Settings\r\n\/\/ sv_location \"eu\"\r\nsv_region \"255\"\r\nsv_lan \"0\"\r\nsv_max_queries_sec_global \"30000\"\r\nsv_max_queries_window \"45\"\r\nsv_max_queries_sec \"5\"\r\n\r\n\/\/ Server Limits\r\nsbox_maxprops\t\t100\r\nsbox_maxragdolls\t5\r\nsbox_maxnpcs\t\t10\r\nsbox_maxballoons\t10\r\nsbox_maxeffects\t\t10\r\nsbox_maxdynamite\t10\r\nsbox_maxlamps\t\t10\r\nsbox_maxthrusters\t10\r\nsbox_maxwheels\t\t10\r\nsbox_maxhoverballs\t10\r\nsbox_maxvehicles\t20\r\nsbox_maxbuttons\t\t10\r\nsbox_maxsents\t\t20\r\nsbox_maxemitters\t5\r\nsbox_godmode\t\t0\r\nsbox_noclip\t\t 0\r\n\r\n\/\/ Network Settings - Please keep these set to default.\r\n\r\nsv_minrate\t\t75000\r\nsv_maxrate\t\t0\r\ngmod_physiterations\t2\r\nnet_splitpacket_maxrate\t45000\r\ndecalfrequency\t\t12 \r\n\r\n\/\/ Execute Ban Files - Please do not edit\r\nexec banned_ip.cfg \r\nexec banned_user.cfg \r\n\r\n\/\/ Add custom lines under here\r\n' > server.cfg",
"container": "ghcr.io\/pterodactyl\/installers:debian",
"entrypoint": "bash"
}
@ -101,4 +103,4 @@
"rules": "required|boolean"
}
]
}
}

View File

@ -34,7 +34,7 @@ x-common:
#
services:
database:
image: library/mysql:8.0
image: mariadb:10.5
restart: always
command: --default-authentication-plugin=mysql_native_password
volumes:

View File

@ -37,7 +37,7 @@
"swr": "^0.2.3",
"tailwindcss": "^2.0.2",
"uuid": "^3.3.2",
"xterm": "^4.12.0",
"xterm": "^4.15.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.4.0",
"xterm-addon-search": "^0.7.0",

View File

@ -7,10 +7,21 @@ const http: AxiosInstance = axios.create({
'X-Requested-With': 'XMLHttpRequest',
Accept: 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': (window as any).X_CSRF_TOKEN as string || '',
},
});
http.interceptors.request.use(req => {
const cookies = document.cookie.split(';').reduce((obj, val) => {
const [ key, value ] = val.trim().split('=').map(decodeURIComponent);
return { ...obj, [key]: value };
}, {} as Record<string, string>);
req.headers['X-XSRF-TOKEN'] = cookies['XSRF-TOKEN'] || 'nil';
return req;
});
http.interceptors.request.use(req => {
if (!req.url?.endsWith('/resources') && (req.url?.indexOf('_debugbar') || -1) < 0) {
store.getActions().progress.startContinuous();

View File

@ -47,7 +47,7 @@ export default () => {
>
<UpdateEmailAddressForm/>
</ContentBox>
<ContentBox css={tw`xl:ml-8 mt-8 xl:mt-0`} title={'Configure Two Factor'}>
<ContentBox css={tw`lg:ml-8 mt-8 lg:mt-0`} title={'Configure Two Factor'}>
<ConfigureTwoFactorForm/>
</ContentBox>
</Container>

View File

@ -4,7 +4,7 @@ import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome
import { Link } from 'react-router-dom';
import { Server } from '@/api/server/getServer';
import getServerResourceUsage, { ServerPowerState, ServerStats } from '@/api/server/getServerResourceUsage';
import { bytesToHuman, megabytesToHuman } from '@/helpers';
import { bytesToHuman, megabytesToHuman, formatIp } from '@/helpers';
import tw from 'twin.macro';
import GreyRowBox from '@/components/elements/GreyRowBox';
import Spinner from '@/components/elements/Spinner';
@ -97,7 +97,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
{
server.allocations.filter(alloc => alloc.isDefault).map(allocation => (
<React.Fragment key={allocation.ip + allocation.port.toString()}>
{allocation.alias || allocation.ip}:{allocation.port}
{allocation.alias || formatIp(allocation.ip)}:{allocation.port}
</React.Fragment>
))
}

View File

@ -13,7 +13,7 @@ import { Link } from 'react-router-dom';
import styled from 'styled-components/macro';
import tw from 'twin.macro';
import Input from '@/components/elements/Input';
import { formatIp } from '@/helpers';
type Props = RequiredModalProps;
interface Values {
@ -109,7 +109,7 @@ export default ({ ...props }: Props) => {
<p css={tw`mt-1 text-xs text-neutral-400`}>
{
server.allocations.filter(alloc => alloc.isDefault).map(allocation => (
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || formatIp(allocation.ip)}:{allocation.port}</span>
))
}
</p>

View File

@ -7,7 +7,7 @@ import ServerContentBlock from '@/components/elements/ServerContentBlock';
import ServerDetailsBlock from '@/components/server/ServerDetailsBlock';
import isEqual from 'react-fast-compare';
import PowerControls from '@/components/server/PowerControls';
import { EulaModalFeature, JavaVersionModalFeature } from '@feature/index';
import { EulaModalFeature, JavaVersionModalFeature, GSLTokenModalFeature } from '@feature/index';
import ErrorBoundary from '@/components/elements/ErrorBoundary';
import Spinner from '@/components/elements/Spinner';
@ -60,6 +60,7 @@ const ServerConsole = () => {
<React.Suspense fallback={null}>
{eggFeatures.includes('eula') && <EulaModalFeature/>}
{eggFeatures.includes('java_version') && <JavaVersionModalFeature/>}
{eggFeatures.includes('gsl_token') && <GSLTokenModalFeature/>}
</React.Suspense>
</div>
</ServerContentBlock>

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import tw, { TwStyle } from 'twin.macro';
import { faCircle, faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { bytesToHuman, megabytesToHuman } from '@/helpers';
import { bytesToHuman, megabytesToHuman, formatIp } from '@/helpers';
import TitledGreyBox from '@/components/elements/TitledGreyBox';
import { ServerContext } from '@/state/server';
import CopyOnClick from '@/components/elements/CopyOnClick';
@ -72,7 +72,7 @@ const ServerDetailsBlock = () => {
const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring);
const limits = ServerContext.useStoreState(state => state.server.data!.limits);
const primaryAllocation = ServerContext.useStoreState(state => state.server.data!.allocations.filter(alloc => alloc.isDefault).map(
allocation => (allocation.alias || allocation.ip) + ':' + allocation.port,
allocation => (allocation.alias || formatIp(allocation.ip)) + ':' + allocation.port,
)).toString();
const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited';

View File

@ -97,6 +97,7 @@ export default () => {
setCpu(
new Chart(node.getContext('2d')!, chartDefaults({
callback: (value) => `${value}% `,
suggestedMax: limits.cpu,
})),
);
}, []);

View File

@ -76,7 +76,7 @@ export default ({ database, className }: Props) => {
<FlashMessageRender byKey={'database:delete'} css={tw`mb-6`}/>
<h2 css={tw`text-2xl mb-6`}>Confirm database deletion</h2>
<p css={tw`text-sm`}>
Deleting a database is a permanent action, it cannot be undone. This will permanetly
Deleting a database is a permanent action, it cannot be undone. This will permanently
delete the <strong>{database.name}</strong> database and remove all associated data.
</p>
<Form css={tw`m-0 mt-6`}>

View File

@ -0,0 +1,101 @@
import React, { useEffect, useState } from 'react';
import { ServerContext } from '@/state/server';
import Modal from '@/components/elements/Modal';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash';
import { SocketEvent, SocketRequest } from '@/components/server/events';
import Field from '@/components/elements/Field';
import updateStartupVariable from '@/api/server/updateStartupVariable';
import { Form, Formik } from 'formik';
interface Values {
gslToken: string;
}
const GSLTokenModalFeature = () => {
const [ visible, setVisible ] = useState(false);
const [ loading, setLoading ] = useState(false);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const status = ServerContext.useStoreState(state => state.status.value);
const { clearFlashes, clearAndAddHttpError } = useFlash();
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
useEffect(() => {
if (!connected || !instance || status === 'running') return;
const errors = [
'(gsl token expired)',
'(account not found)',
];
const listener = (line: string) => {
if (errors.some(p => line.toLowerCase().includes(p))) {
setVisible(true);
}
};
instance.addListener(SocketEvent.CONSOLE_OUTPUT, listener);
return () => {
instance.removeListener(SocketEvent.CONSOLE_OUTPUT, listener);
};
}, [ connected, instance, status ]);
const updateGSLToken = (values: Values) => {
setLoading(true);
clearFlashes('feature:gslToken');
updateStartupVariable(uuid, 'STEAM_ACC', values.gslToken)
.then(() => {
if (instance) {
instance.send(SocketRequest.SET_STATE, 'restart');
}
setLoading(false);
setVisible(false);
})
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'feature:gslToken', error });
})
.then(() => setLoading(false));
};
useEffect(() => {
clearFlashes('feature:gslToken');
}, []);
return (
<Formik
onSubmit={updateGSLToken}
initialValues={{ gslToken: '' }}
>
<Modal visible={visible} onDismissed={() => setVisible(false)} closeOnBackground={false} showSpinnerOverlay={loading}>
<FlashMessageRender key={'feature:gslToken'} css={tw`mb-4`}/>
<Form>
<h2 css={tw`text-2xl mb-4 text-neutral-100`}>Invalid GSL token!</h2>
<p css={tw`mt-4`}>It seems like your Gameserver Login Token (GSL token) is invalid or has expired.</p>
<p css={tw`mt-4`}>You can either generate a new one and enter it below or leave the field blank to remove it completely.</p>
<div css={tw`sm:flex items-center mt-4`}>
<Field
name={'gslToken'}
label={'GSL Token'}
description={'Visit https://steamcommunity.com/dev/managegameservers to generate a token.'}
autoFocus
/>
</div>
<div css={tw`mt-8 sm:flex items-center justify-end`}>
<Button type={'submit'} css={tw`mt-4 sm:mt-0 sm:ml-4 w-full sm:w-auto`}>
Update GSL Token
</Button>
</div>
</Form>
</Modal>
</Formik>
);
};
export default GSLTokenModalFeature;

View File

@ -10,15 +10,16 @@ import { SocketEvent, SocketRequest } from '@/components/server/events';
import Select from '@/components/elements/Select';
const dockerImageList = [
{ name: 'Java 8', image: 'ghcr.io/pterodactyl/yolks:java_8' },
{ name: 'Java 11', image: 'ghcr.io/pterodactyl/yolks:java_11' },
{ name: 'Java 17', image: 'ghcr.io/pterodactyl/yolks:java_17' },
{ name: 'Java 16', image: 'ghcr.io/pterodactyl/yolks:java_16' },
{ name: 'Java 11', image: 'ghcr.io/pterodactyl/yolks:java_11' },
{ name: 'Java 8', image: 'ghcr.io/pterodactyl/yolks:java_8' },
];
const JavaVersionModalFeature = () => {
const [ visible, setVisible ] = useState(false);
const [ loading, setLoading ] = useState(false);
const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_16');
const [ selectedVersion, setSelectedVersion ] = useState('ghcr.io/pterodactyl/yolks:java_17');
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const status = ServerContext.useStoreState(state => state.status.value);
@ -30,6 +31,7 @@ const JavaVersionModalFeature = () => {
const errors = [
'minecraft 1.17 requires running the server with java 16 or above',
'minecraft 1.18 requires running the server with java 17 or above',
'java.lang.unsupportedclassversionerror',
'unsupported major.minor version',
'has been compiled by a more recent version of the java runtime',

View File

@ -8,5 +8,6 @@ import { lazy } from 'react';
*/
const EulaModalFeature = lazy(() => import(/* webpackChunkName: "feature.eula" */'@feature/eula/EulaModalFeature'));
const JavaVersionModalFeature = lazy(() => import(/* webpackChunkName: "feature.java_version" */'@feature/JavaVersionModalFeature'));
const GSLTokenModalFeature = lazy(() => import(/* webpackChunkName: "feature.gsl_token" */'@feature/GSLTokenModalFeature'));
export { EulaModalFeature, JavaVersionModalFeature };
export { EulaModalFeature, JavaVersionModalFeature, GSLTokenModalFeature };

View File

@ -18,6 +18,7 @@ import CopyOnClick from '@/components/elements/CopyOnClick';
import DeleteAllocationButton from '@/components/server/network/DeleteAllocationButton';
import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation';
import getServerAllocations from '@/api/swr/getServerAllocations';
import { formatIp } from '@/helpers';
const Code = styled.code`${tw`font-mono py-1 px-2 bg-neutral-900 rounded text-sm inline-block`}`;
const Label = styled.label`${tw`uppercase text-xs mt-1 text-neutral-400 block px-1 select-none transition-colors duration-150`}`;
@ -66,7 +67,7 @@ const AllocationRow = ({ allocation }: Props) => {
<div css={tw`mr-4 flex-1 md:w-40`}>
{allocation.alias ?
<CopyOnClick text={allocation.alias}><Code css={tw`w-40 truncate`}>{allocation.alias}</Code></CopyOnClick> :
<CopyOnClick text={allocation.ip}><Code>{allocation.ip}</Code></CopyOnClick>}
<CopyOnClick text={formatIp(allocation.ip)}><Code>{formatIp(allocation.ip)}</Code></CopyOnClick>}
<Label>{allocation.alias ? 'Hostname' : 'IP Address'}</Label>
</div>
<div css={tw`w-16 md:w-24 overflow-hidden`}>

View File

@ -13,6 +13,7 @@ import { LinkButton } from '@/components/elements/Button';
import ServerContentBlock from '@/components/elements/ServerContentBlock';
import isEqual from 'react-fast-compare';
import CopyOnClick from '@/components/elements/CopyOnClick';
import { formatIp } from '@/helpers';
export default () => {
const username = useStoreState(state => state.user.data!.username);
@ -30,10 +31,10 @@ export default () => {
<TitledGreyBox title={'SFTP Details'} css={tw`mb-6 md:mb-10`}>
<div>
<Label>Server Address</Label>
<CopyOnClick text={`sftp://${sftp.ip}:${sftp.port}`}>
<CopyOnClick text={`sftp://${formatIp(sftp.ip)}:${sftp.port}`}>
<Input
type={'text'}
value={`sftp://${sftp.ip}:${sftp.port}`}
value={`sftp://${formatIp(sftp.ip)}:${sftp.port}`}
readOnly
/>
</CopyOnClick>
@ -59,7 +60,7 @@ export default () => {
<div css={tw`ml-4`}>
<LinkButton
isSecondary
href={`sftp://${username}.${id}@${sftp.ip}:${sftp.port}`}
href={`sftp://${username}.${id}@${formatIp(sftp.ip)}:${sftp.port}`}
>
Launch SFTP
</LinkButton>

View File

@ -63,3 +63,7 @@ export function encodePathSegments (path: string): string {
export function hashToPath (hash: string): string {
return hash.length > 0 ? decodeURIComponent(hash.substr(1)) : '/';
}
export function formatIp (ip: string): string {
return /([a-f0-9:]+:+)+[a-f0-9]+/.test(ip) ? `[${ip}]` : ip;
}

View File

@ -10,13 +10,7 @@ export interface Mode {
const modes: Mode[] = [
{ name: 'C', mime: 'text/x-csrc', mode: 'clike', ext: [ 'c', 'h', 'ino' ] },
{
name: 'C++',
mime: 'text/x-c++src',
mode: 'clike',
ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ],
alias: [ 'cpp' ],
},
{ name: 'C++', mime: 'text/x-c++src', mode: 'clike', ext: [ 'cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx' ], alias: [ 'cpp' ] },
{ name: 'C#', mime: 'text/x-csharp', mode: 'clike', ext: [ 'cs' ], alias: [ 'csharp', 'cs' ] },
{ name: 'CSS', mime: 'text/css', mode: 'css', ext: [ 'css' ] },
{ name: 'CQL', mime: 'text/x-cassandra', mode: 'sql', ext: [ 'cql' ] },
@ -24,99 +18,34 @@ const modes: Mode[] = [
{ name: 'Dockerfile', mime: 'text/x-dockerfile', mode: 'dockerfile', file: /^Dockerfile$/ },
{ name: 'Git Markdown', mime: 'text/x-gfm', mode: 'gfm', file: /^(readme|contributing|history|license).md$/i },
{ name: 'Golang', mime: 'text/x-go', mode: 'go', ext: [ 'go' ] },
{
name: 'HTML',
mime: 'text/html',
mode: 'htmlmixed',
ext: [ 'html', 'htm', 'handlebars', 'hbs' ],
alias: [ 'xhtml' ],
},
{ name: 'HTML', mime: 'text/html', mode: 'htmlmixed', ext: [ 'html', 'htm', 'handlebars', 'hbs' ], alias: [ 'xhtml' ] },
{ name: 'HTTP', mime: 'message/http', mode: 'http' },
{
name: 'JavaScript',
mime: 'text/javascript',
mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ],
mode: 'javascript',
ext: [ 'js' ],
alias: [ 'ecmascript', 'js', 'node' ],
},
{
name: 'JSON',
mime: 'application/json',
mimes: [ 'application/json', 'application/x-json' ],
mode: 'javascript',
ext: [ 'json', 'map' ],
alias: [ 'json5' ],
},
{ name: 'JavaScript', mime: 'text/javascript', mimes: [ 'text/javascript', 'text/ecmascript', 'application/javascript', 'application/x-javascript', 'application/ecmascript' ], mode: 'javascript', ext: [ 'js' ], alias: [ 'ecmascript', 'js', 'node' ] },
{ name: 'JSON', mime: 'application/json', mimes: [ 'application/json', 'application/x-json' ], mode: 'javascript', ext: [ 'json', 'map' ], alias: [ 'json5' ] },
{ name: 'Lua', mime: 'text/x-lua', mode: 'lua', ext: [ 'lua' ] },
{ name: 'Markdown', mime: 'text/x-markdown', mode: 'markdown', ext: [ 'markdown', 'md', 'mkd' ] },
{ name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql' },
{ name: 'MS SQL', mime: 'text/x-mssql', mode: 'sql' },
{ name: 'MySQL', mime: 'text/x-mysql', mode: 'sql' },
{ name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx', file: /nginx.*\.conf$/i },
{
name: 'PHP',
mime: 'text/x-php',
mimes: [ 'text/x-php', 'application/x-httpd-php', 'application/x-httpd-php-open' ],
mode: 'php',
ext: [ 'php', 'php3', 'php4', 'php5', 'php7', 'phtml' ],
},
{ name: 'PHP', mime: 'text/x-php', mimes: [ 'text/x-php', 'application/x-httpd-php', 'application/x-httpd-php-open' ], mode: 'php', ext: [ 'php', 'php3', 'php4', 'php5', 'php7', 'phtml' ] },
{ name: 'Plain Text', mime: 'text/plain', mode: 'null', ext: [ 'txt', 'text', 'conf', 'def', 'list', 'log' ] },
{ name: 'PostgreSQL', mime: 'text/x-pgsql', mode: 'sql' },
{
name: 'Properties',
mime: 'text/x-properties',
mode: 'properties',
ext: [ 'properties', 'ini', 'in' ],
alias: [ 'ini', 'properties' ],
},
{
name: 'Python',
mime: 'text/x-python',
mode: 'python',
ext: [ 'BUILD', 'bzl', 'py', 'pyw' ],
file: /^(BUCK|BUILD)$/,
},
{
name: 'Ruby',
mime: 'text/x-ruby',
mode: 'ruby',
ext: [ 'rb' ],
alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ],
},
{ name: 'Properties', mime: 'text/x-properties', mode: 'properties', ext: [ 'properties', 'ini', 'in' ], alias: [ 'ini', 'properties' ] },
{ name: 'Pug', mime: 'text/x-pug', mimes: [ 'text/x-pug', 'text/x-jade' ], mode: 'null', ext: [ 'pug' ] },
{ name: 'Python', mime: 'text/x-python', mode: 'python', ext: [ 'BUILD', 'bzl', 'py', 'pyw' ], file: /^(BUCK|BUILD)$/ },
{ name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby', ext: [ 'rb' ], alias: [ 'jruby', 'macruby', 'rake', 'rb', 'rbx' ] },
{ name: 'Rust', mime: 'text/x-rustsrc', mode: 'rust', ext: [ 'rs' ] },
{ name: 'Sass', mime: 'text/x-sass', mode: 'sass', ext: [ 'sass' ] },
{ name: 'SCSS', mime: 'text/x-scss', mode: 'css', ext: [ 'scss' ] },
{
name: 'Shell',
mime: 'text/x-sh',
mimes: [ 'text/x-sh', 'application/x-sh' ],
mode: 'shell',
ext: [ 'sh', 'ksh', 'bash' ],
alias: [ 'bash', 'sh', 'zsh' ],
file: /^PKGBUILD$/,
},
{ name: 'Shell', mime: 'text/x-sh', mimes: [ 'text/x-sh', 'application/x-sh' ], mode: 'shell', ext: [ 'sh', 'ksh', 'bash' ], alias: [ 'bash', 'sh', 'zsh' ], file: /^PKGBUILD$/ },
{ name: 'SQL', mime: 'text/x-sql', mode: 'sql', ext: [ 'sql' ] },
{ name: 'SQLite', mime: 'text/x-sqlite', mode: 'sql' },
{ name: 'TOML', mime: 'text/x-toml', mode: 'toml', ext: [ 'toml' ] },
{ name: 'TypeScript', mime: 'application/typescript', mode: 'javascript', ext: [ 'ts' ], alias: [ 'ts' ] },
{ name: 'Vue', mime: 'script/x-vue', mimes: [ 'script/x-vue', 'text/x-vue' ], mode: 'vue', ext: [ 'vue' ] },
{
name: 'XML',
mime: 'application/xml',
mimes: [ 'application/xml', 'text/xml' ],
mode: 'xml',
ext: [ 'xml', 'xsl', 'xsd', 'svg' ],
alias: [ 'rss', 'wsdl', 'xsd' ],
},
{
name: 'YAML',
mime: 'text/x-yaml',
mimes: [ 'text/x-yaml', 'text/yaml' ],
mode: 'yaml',
ext: [ 'yaml', 'yml' ],
alias: [ 'yml' ],
},
{ name: 'XML', mime: 'application/xml', mimes: [ 'application/xml', 'text/xml' ], mode: 'xml', ext: [ 'xml', 'xsl', 'xsd', 'svg' ], alias: [ 'rss', 'wsdl', 'xsd' ] },
{ name: 'YAML', mime: 'text/x-yaml', mimes: [ 'text/x-yaml', 'text/yaml' ], mode: 'yaml', ext: [ 'yaml', 'yml' ], alias: [ 'yml' ] },
];
export default modes;

View File

@ -70,7 +70,11 @@
@parent
<script>
$('#configTokenBtn').on('click', function (event) {
$.getJSON('{{ route('admin.nodes.view.configuration.token', $node->id) }}').done(function (data) {
$.ajax({
method: 'POST',
url: '{{ route('admin.nodes.view.configuration.token', $node->id) }}',
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
}).done(function (data) {
swal({
type: 'success',
title: 'Token created.',

View File

@ -145,9 +145,9 @@
showLoaderOnConfirm: true
}, function () {
$.ajax({
method: 'GET',
method: 'POST',
url: '/admin/settings/mail/test',
headers: { 'X-CSRF-Token': $('input[name="_token"]').val() }
headers: { 'X-CSRF-TOKEN': $('input[name="_token"]').val() }
}).fail(function (jqXHR) {
showErrorDialog(jqXHR, 'test');
}).done(function () {

View File

@ -66,8 +66,8 @@ Route::group(['prefix' => 'databases'], function () {
Route::group(['prefix' => 'settings'], function () {
Route::get('/', 'Settings\IndexController@index')->name('admin.settings');
Route::get('/mail', 'Settings\MailController@index')->name('admin.settings.mail');
Route::get('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test');
Route::get('/advanced', 'Settings\AdvancedController@index')->name('admin.settings.advanced');
Route::post('/mail/test', 'Settings\MailController@test')->name('admin.settings.mail.test');
Route::patch('/', 'Settings\IndexController@update');
Route::patch('/mail', 'Settings\MailController@update');
@ -153,12 +153,12 @@ Route::group(['prefix' => 'nodes'], function () {
Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation');
Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers');
Route::get('/view/{node}/system-information', 'Nodes\SystemInformationController');
Route::get('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token');
Route::post('/new', 'NodesController@store');
Route::post('/view/{node}/allocation', 'NodesController@createAllocation');
Route::post('/view/{node}/allocation/remove', 'NodesController@allocationRemoveBlock')->name('admin.nodes.view.allocation.removeBlock');
Route::post('/view/{node}/allocation/alias', 'NodesController@allocationSetAlias')->name('admin.nodes.view.allocation.setAlias');
Route::post('/view/{node}/settings/token', 'NodeAutoDeployController')->name('admin.nodes.view.configuration.token');
Route::patch('/view/{node}/settings', 'NodesController@updateSettings');

View File

@ -5,6 +5,7 @@ namespace Pterodactyl\Tests\Integration\Api\Application\Location;
use Pterodactyl\Models\Node;
use Illuminate\Http\Response;
use Pterodactyl\Models\Location;
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
use Pterodactyl\Transformers\Api\Application\NodeTransformer;
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
use Pterodactyl\Tests\Integration\Api\Application\ApplicationApiIntegrationTestCase;
@ -88,6 +89,77 @@ class LocationControllerTest extends ApplicationApiIntegrationTestCase
], true);
}
/**
* Test that a location can be created.
*/
public function testCreateLocation()
{
$response = $this->postJson('/api/application/locations', [
'short' => 'inhouse',
'long' => 'This is my inhouse location',
]);
$response->assertStatus(Response::HTTP_CREATED);
$response->assertJsonCount(3);
$response->assertJsonStructure([
'object',
'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at'],
'meta' => ['resource'],
]);
$this->assertDatabaseHas('locations', ['short' => 'inhouse', 'long' => 'This is my inhouse location']);
$location = Location::where('short', 'inhouse')->first();
$response->assertJson([
'object' => 'location',
'attributes' => $this->getTransformer(LocationTransformer::class)->transform($location),
'meta' => [
'resource' => route('api.application.locations.view', $location->id),
],
], true);
}
/**
* Test that a location can be updated.
*/
public function testUpdateLocation()
{
$location = Location::factory()->create();
$response = $this->patchJson('/api/application/locations/' . $location->id, [
'short' => 'new inhouse',
'long' => 'This is my new inhouse location'
]);
$response->assertStatus(Response::HTTP_OK);
$response->assertJsonCount(2);
$response->assertJsonStructure([
'object',
'attributes' => ['id', 'short', 'long', 'created_at', 'updated_at']
]);
$this->assertDatabaseHas('locations', ['short' => 'new inhouse', 'long' => 'This is my new inhouse location']);
$location = $location->fresh();
$response->assertJson([
'object' => 'location',
'attributes' => $this->getTransformer(LocationTransformer::class)->transform($location),
]);
}
/**
* Test that a location can be deleted from the database.
*/
public function testDeleteLocation()
{
$location = Location::factory()->create();
$this->assertDatabaseHas('locations', ['id' => $location->id]);
$response = $this->delete('/api/application/locations/' . $location->id);
$response->assertStatus(Response::HTTP_NO_CONTENT);
$this->assertDatabaseMissing('locations', ['id' => $location->id]);
}
/**
* Test that all of the defined relationships for a location can be loaded successfully.
*/

View File

@ -26,7 +26,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase
}
/**
* Test that all of the eggs belonging to a given nest can be returned.
* Test that all the eggs belonging to a given nest can be returned.
*/
public function testListAllEggsInNest()
{
@ -47,7 +47,7 @@ class EggControllerTest extends ApplicationApiIntegrationTestCase
'files' => [],
'startup' => ['done'],
'stop',
'logs' => ['custom', 'location'],
'logs' => [],
'extends',
],
],

View File

@ -1,168 +0,0 @@
<?php
namespace Pterodactyl\Tests\Unit\Http\Middleware\Api;
use Mockery as m;
use Carbon\CarbonImmutable;
use Pterodactyl\Models\User;
use Pterodactyl\Models\ApiKey;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Http\Middleware\Api\AuthenticateKey;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AuthenticateKeyTest extends MiddlewareTestCase
{
/**
* @var \Illuminate\Auth\AuthManager|\Mockery\Mock
*/
private $auth;
/**
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
*/
private $encrypter;
/**
* @var \Pterodactyl\Contracts\Repository\ApiKeyRepositoryInterface|\Mockery\Mock
*/
private $repository;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->auth = m::mock(AuthManager::class);
$this->encrypter = m::mock(Encrypter::class);
$this->repository = m::mock(ApiKeyRepositoryInterface::class);
}
/**
* Test that a missing bearer token will throw an exception.
*/
public function testMissingBearerTokenThrowsException()
{
$this->request->shouldReceive('user')->andReturnNull();
$this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull();
try {
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
} catch (HttpException $exception) {
$this->assertEquals(401, $exception->getStatusCode());
$this->assertEquals(['WWW-Authenticate' => 'Bearer'], $exception->getHeaders());
}
}
/**
* Test that an invalid API identifier throws an exception.
*/
public function testInvalidIdentifier()
{
$this->expectException(AccessDeniedHttpException::class);
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234');
$this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException());
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
}
/**
* Test that a valid token can continue past the middleware.
*/
public function testValidToken()
{
$model = ApiKey::factory()->make();
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted');
$this->repository->shouldReceive('findFirstWhere')->with([
['identifier', '=', $model->identifier],
['key_type', '=', ApiKey::TYPE_APPLICATION],
])->once()->andReturn($model);
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
$this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull();
$this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
'last_used_at' => CarbonImmutable::now(),
])->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
$this->assertEquals($model, $this->request->attributes->get('api_key'));
}
/**
* Test that a valid token can continue past the middleware when set as a user token.
*/
public function testValidTokenWithUserKey()
{
$model = ApiKey::factory()->make();
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'decrypted');
$this->repository->shouldReceive('findFirstWhere')->with([
['identifier', '=', $model->identifier],
['key_type', '=', ApiKey::TYPE_ACCOUNT],
])->once()->andReturn($model);
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
$this->auth->shouldReceive('guard->loginUsingId')->with($model->user_id)->once()->andReturnNull();
$this->repository->shouldReceive('withoutFreshModel->update')->with($model->id, [
'last_used_at' => CarbonImmutable::now(),
])->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT);
$this->assertEquals($model, $this->request->attributes->get('api_key'));
}
/**
* Test that we can still make it though this middleware if the user is logged in and passing
* through a cookie.
*/
public function testAccessWithoutToken()
{
$user = User::factory()->make(['id' => 123]);
$this->request->shouldReceive('user')->andReturn($user);
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_ACCOUNT);
$model = $this->request->attributes->get('api_key');
$this->assertSame(ApiKey::TYPE_ACCOUNT, $model->key_type);
$this->assertSame(123, $model->user_id);
$this->assertNull($model->identifier);
}
/**
* Test that a valid token identifier with an invalid token attached to it
* triggers an exception.
*/
public function testInvalidTokenForIdentifier()
{
$this->expectException(AccessDeniedHttpException::class);
$model = ApiKey::factory()->make();
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf');
$this->repository->shouldReceive('findFirstWhere')->with([
['identifier', '=', $model->identifier],
['key_type', '=', ApiKey::TYPE_APPLICATION],
])->once()->andReturn($model);
$this->encrypter->shouldReceive('decrypt')->with($model->token)->once()->andReturn('decrypted');
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions(), ApiKey::TYPE_APPLICATION);
}
/**
* Return an instance of the middleware with mocked dependencies for testing.
*/
private function getMiddleware(): AuthenticateKey
{
return new AuthenticateKey($this->repository, $this->auth, $this->encrypter);
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace Pterodactyl\Tests\Unit\Http\Middleware\Api;
use Mockery as m;
use Illuminate\Contracts\Config\Repository;
use Pterodactyl\Http\Middleware\Api\SetSessionDriver;
use Pterodactyl\Tests\Unit\Http\Middleware\MiddlewareTestCase;
class SetSessionDriverTest extends MiddlewareTestCase
{
/**
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
*/
private $config;
/**
* Setup tests.
*/
public function setUp(): void
{
parent::setUp();
$this->config = m::mock(Repository::class);
}
/**
* Test that a production environment does not try to disable debug bar.
*/
public function testMiddleware()
{
$this->config->shouldReceive('set')->once()->with('session.driver', 'array')->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
}
/**
* Return an instance of the middleware with mocked dependencies for testing.
*/
private function getMiddleware(): SetSessionDriver
{
return new SetSessionDriver($this->config);
}
}

View File

@ -8040,10 +8040,10 @@ xterm-addon-web-links@^0.4.0:
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03"
integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg==
xterm@^4.12.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.12.0.tgz#db09b425b4dcae5b96f8cbbaaa93b3bc60997ca9"
integrity sha512-K5mF/p3txUV18mjiZFlElagoHFpqXrm5OYHeoymeXSu8GG/nMaOO/+NRcNCwfdjzAbdQ5VLF32hEHiWWKKm0bw==
xterm@^4.15.0:
version "4.15.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0.tgz#e52038507eba7e0d36d47f81e29fe548c82b9561"
integrity sha512-Ik1GoSq1yqKZQ2LF37RPS01kX9t4TP8gpamUYblD09yvWX5mEYuMK4CcqH6+plgiNEZduhTz/UrcaWs97gOlOw==
y18n@^4.0.0:
version "4.0.0"