Skull and Roses is a simple card game. You can read more about it here. This repository contains a game server implementation of the game logic and all the surrounding infrastructure, for example authentication, game rooms, and hosting multiple running games at once.
This is an HTTP(S) server (there might be a WebSockets part later, to avoid polling logic in the clients) that uses JWK for authentication. It is implemented with Ktor and also uses Logback and kotlinx-datetime. Additionally, there's a dependency on SkullGame Common, which is not published on any public package registry. Therefore, in order to build this server, you have to first build skullgame-common and install it into your local maven repository (see 'Building' for more information).
/login
: authenticate via JWK and get a token for all the other endpoints/player/hello
: validate auth/player/games
: query which games you have joined/newgame
: open up a new game room/join/{gameid}
: join a game room/gameinfo/{gameid}
: query information about all users in the game room/startgame/{gameid}
: start a game (if you're the initiator)/move/{gameid}
: submit a move to a running game/masterstate/{gameid}
: query the master state of a game (if you're an admin)/state/{gameid}
: query your individual player state/.well-known/jwks.json
: JWK Set for key validation
SkullGame Server uses Gradle as a build system. To build and run this server, it is generally
enough to invoke gradlew run
(or gradlew.bat run
on windows).
All dependencies will be pulled in automatically, but
there is one exception: com.rtarita.skull:skullgame-common
contains common code that can be shared between server and
client. You can find the repository on GitHub as well, but there
are no published packages of it, which means you have to build it yourself.
Another important thing to note is that the server expects a server-config
directory in the same folder as it is run
in. This directory has to contain the following files:
certs/jwks.json
: JWK Set, see 'Configuration' for details on how to generateconfig.json
: general-purpose configurations for the server, see 'Configuration' for detailed infoid_rsa
: Matching private key tojwks.json
.
Ultimately, this means you have to progress through the following steps to build and run this server:
- Clone skullgame-common
- Follow the instructions from its README to build it and install it into the local maven repository
- Create a new directory called
server-config
directly below the root directory of this project - Fill that directory with the files mentioned above (see 'Configuration' for more info)
- Invoke
gradlew run
/gradlew.bat run
in a terminal
JWK needs a private/public keypair to create and sign the tokens that are used for authentication in this server. However, the public key needs to be in a special JSON format. In order to obtain a (correctly formatted) keypair, you can use mkjwk.
Caution
mkjwk will generate a jwks.json
file that also contains the private key! If you leave the
private key in the file, it will be published by your server, completely exposing every single endpoint and allowing
arbitrary user spoofing. READ THE STEPS BELOW CAREFULLY TO AVOID THIS
- Go to https://mkjwk.org/
- Select "RSA" (should be preselected)
- Key Size: The default is 2048, which is fine. If you know what you are doing, you can change this
- Key Use: Signature
- Algorithm: RSA256 (RSASSA-PKCS1-v1_5 using SHA-256)
- Key ID: SHA-256
- Show X.509: Yes
- Click 'Generate'
- Copy 'Private Key (X.509 PEM Format)' and paste it into the
id_rsa
file, then remove the 'BEGIN PRIVATE KEY' and ' END PRIVATE KEY' lines. This is your private key, keep it secret at all times. - The correct format for the
certs/jwks.json
file is only found in the 'Public and Private Keypair Set' result, but this contains the private key as well. Therefore, DO NOT COPY the 'Public and Private Keypair Set' result. Instead, put the following JSON structure into the file:Then, copy the 'Public Key' result from mkjwk and paste it inside the square brackets of the{ "keys": [ ] }
keys
property in your JSON. This will now only contain the public key. Here is an example of how yourjwks.json
might look like:If your file contains any other properties than the ones in this example, REMOVE THEM.{ "keys": [ { "kty": "RSA", "e": "AQAB", "use": "sig", "kid": "ryjwaNbswkJDS4HTUD07_DVTglMUL7H9sblugWJTd5s", "alg": "RS256", "n": "wKJ3kS-iB54CD8hB8QkOfX9C3oLJC8NkEKmrjH3gEy7gw... (and so on)" } ] }
This is all you have to do for your server-config/id_rsa
and server-config/certs/jwks.json
files.
The configuration file for the SkullGame Server has the following structure:
{
"auth": {
"issuer": "<some url>",
"audience": "<some url>",
"realm": "<short description>",
"kid": "a JWK key ID"
},
"users": [
{
"id": "<user id>",
"displayName": "<user display name>",
"passHash": "<hash of users password>",
"isAdmin": false
},
...
],
"environment": {
"host": "<some host>",
"subdomain": "<some subdomain>",
"port": 8443,
"maxManagerThreads": 4
},
"features": {
"reverseProxy": false,
"autoHeadResponse": true,
"siteServing": false
}
}
Explanation:
auth.issuer
: the JWK Issuer URL, in this case it's just the URL on which the server is runningauth.audience
: you can re-use the issuer URL for thisauth.realm
: something like "access to the SkullGame Server"auth.kid
: copy and paste thekid
property of yourcerts/jwks.json
file into this propertyusers[]
: in this array you can define all the users that have access to the game server and can use their account to play gamesusers[].id
: unique user ID that is used for logging inusers[].displayName
: the users' display nameusers[].passHash
: the SHA-256 hash (hexadecimal, lowercase) of the users' passwordusers[].admin
(default:false
): whether the user has admin rights or notenvironment.host
: The host domain on which the server is runningenvironment.port
(default:8443
): The port on which the server should listenenvironment.maxManagerThreads
(default:4
): The maximum number of threads that will be allocated specifically to games management. The actual number of threads will be at most this number, but capped by the number of logical processors of the machinefeatures.reverseProxy
(default:false
): set this totrue
if you're running your server behind a reverse proxy (it will cause IPs to be resolved correctly for logging and similar stuff). If you don't run the server behind a reverse proxy, set this tofalse
!features.autoHeadResponse
(default:true
): Whether the server should automatically answerHEAD
requests for its endpointsfeatures.siteServing
(default:false
): Serve a static website alongside your server. The static site will be available without authentication. If set totrue
, the server will look for files in a directory calledsite
(at the same location as theserver-config
directory) and statically serve all contents of that directory from the/
route.
Once you have configured all these settings and put them in server-config/config.json
, you are ready to run the
server!