diff --git a/index.js b/index.js index a1fe064..9b6dd52 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,10 @@ const path = require('path'); const fs = require('fs'); const request = require('sync-request'); +const fetch = require('node-fetch'); +var session = require('express-session'); +const fileUpload = require('express-fileupload'); +var sizeOf = require('image-size'); function downloadFileSync(url) { return require('child_process') @@ -371,6 +375,23 @@ function get_cape(uname) { } } +// oauth_to_uuid +// Purpose: Uses mc-oauth.net to translate an OAuth code into a UUID. +// Returns: Nothing - this is a callback based function. + +function oAuthToUUID(token, cb) { + return fetch('https://mc-oauth.net/api/api?token', { + method: "GET", + headers: { + "token": token + } + }).then(response => { + return response.json(); + }).then(json => { + cb(json); + }); +} + // rm_dir // Purpose: removes all files in a directory. // Returns: nothing. @@ -417,16 +438,232 @@ rm_dir(Cape_CacheLocation); console.log('[CACHE] Cape cache wipe completed.') // Initialise express. -const express = require('express') +const express = require('express'); +const { O_TRUNC } = require('constants'); +const { resourceUsage } = require('process'); const app = express() -const port = 80 +const port = process.env.NODE_PORT || 6481 + +// Initialise session cookie middleware. +app.set('trust proxy', 1) // trust first proxy +app.use(session({ + secret: process.env.COOKIE_SECRET, + resave: false, + saveUninitialized: true, + cookie: { secure: false, maxAge: 7200000 } +})) // Initialise static folders. app.use(express.static('static')) app.use('/MinecraftResources', express.static('resources')) +// Initialise middleware. +app.use(express.urlencoded()); + +app.use(fileUpload()); + // Initialise routers. +// POST /authenticate +// Parameters: +// code: OAuth code from MC-OAuth. +// Returns: +// 301 redirect to appropriate page. + +app.post('/authenticate', (req,res) => { + oAuthToUUID(req.body.code, (response) => { + if (response.status !== "fail") { + // add to session + req.session.username = response.username + req.session.uuid = response.uuid + + // redirect + res.redirect('/skin_landing') + } else { + // throw back to auth page. + res.redirect('/auth.html') + } + }) +}) + +// GET /end_session +// No parameters. +// Returns: +// redirect to home page with user session expired. + +app.get('/end_session', (req,res) => { + req.session.regenerate(function(err) { + // will have a new session here + res.redirect('/') + }) +}) + +// GET /skin_landing +// No parameters. +// Returns: +// landing page + +app.get('/skin_landing', (req,res) => { + if (req.session.username !== undefined) { + let temp = fs.readFileSync(path.join(__dirname, 'templates', 'landing.html')).toString(); + temp = temp.replace('%%USERNAME%%', req.session.username) + temp = temp.replace('%%USERNAME%%', req.session.username) + temp = temp.replace('%%USERNAME%%', req.session.username) + temp = temp.replace('%%USERNAME%%', req.session.username) + temp = temp.replace('%%USERNAME%%', req.session.username) + temp = temp.replace('%%USERNAME%%', req.session.username) + res.send(temp) + } else { + res.redirect('/') + } +}) + +// GET /skin_change +// No parameters +// Returns: +// skin changing page + +app.get('/skin_change', (req,res) => { + if (req.session.username !== undefined) { + let temp = fs.readFileSync(path.join(__dirname, 'templates', 'skin_change.html')).toString(); + temp = temp.replace('%%USERNAME%%', req.session.username) + res.send(temp) + } else { + res.redirect('/') + } +}) + +// POST /upload_skin +// No parameters. +// Returns: +// success or fail page + +app.post('/upload_skin', (req,res) => { + if (req.session.username == undefined) { + // Don't even look at it. + return res.redirect('/') + } + + if (!req.files || Object.keys(req.files).length === 0) { + return res.redirect('/skin_change'); + } + + if (req.files.upload.mimetype != "image/png") { + return res.redirect('/skin_change'); + } + + // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file + let sampleFile = req.files.upload; + + // Use the mv() method to place the file somewhere on your server + sampleFile.mv(path.join(Skin_OverrideLocation, req.session.username + '.png'), function(err) { + if (err) { + return res.redirect('/failure'); + } + + var dimensions = sizeOf(path.join(Skin_OverrideLocation, req.session.username + '.png')); + + if (dimensions.width == "64" && dimensions.height == "32") { + // classic steve skin + res.redirect('/success'); + } else if (dimensions.width == "64" && dimensions.height == "64") { + // non-classic skins (alex or something) + res.redirect('/success') + } else { + // user must be trying to test us. + fs.unlinkSync(path.join(Skin_OverrideLocation, req.session.username + '.png')) + res.sendFile(path.join(__dirname, 'templates', 'size_too_big.html')) + } + + }); +}) + +// GET /cape_change +// No parameters +// Returns: +// cape changing page + +app.get('/cape_change', (req,res) => { + if (req.session.username !== undefined) { + let temp = fs.readFileSync(path.join(__dirname, 'templates', 'cape_change.html')).toString(); + temp = temp.replace('%%USERNAME%%', req.session.username) + res.send(temp) + } else { + res.redirect('/') + } +}) + +// POST /upload_cape +// No parameters. +// Returns: +// success or fail page + +app.post('/upload_cape', (req,res) => { + if (req.session.username == undefined) { + // Don't even look at it. + return res.redirect('/') + } + + if (!req.files || Object.keys(req.files).length === 0) { + return res.redirect('/cape_change'); + } + + if (req.files.upload.mimetype != "image/png") { + return res.redirect('/cape_change'); + } + + // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file + let sampleFile = req.files.upload; + + // Use the mv() method to place the file somewhere on your server + sampleFile.mv(path.join(Cape_OverrideLocation, req.session.username + '.png'), function(err) { + if (err) { + return res.redirect('/failure'); + } + + var dimensions = sizeOf(path.join(Cape_OverrideLocation, req.session.username + '.png')); + + if (dimensions.width == "64" && dimensions.height == "32") { + // cape, GOOD + res.redirect('/success'); + } else { + // user must be trying to test us. + fs.unlinkSync(path.join(Cape_OverrideLocation, req.session.username + '.png')) + res.sendFile(path.join(__dirname, 'templates', 'size_too_big.html')) + } + }); +}) + +// GET /success +// No parameters. +// Returns: +// success page + +app.get('/success', (req,res) => { + if (req.session.username !== undefined) { + let temp = fs.readFileSync(path.join(__dirname, 'templates', 'success.html')).toString(); + temp = temp.replace('%%USERNAME%%', req.session.username) + res.send(temp) + } else { + res.redirect('/') + } +}) + +// GET /failure +// No parameters. +// Returns: +// failure page + +app.get('/failure', (req,res) => { + if (req.session.username !== undefined) { + let temp = fs.readFileSync(path.join(__dirname, 'templates', 'failure.html')).toString(); + temp = temp.replace('%%USERNAME%%', req.session.username) + res.send(temp) + } else { + res.redirect('/') + } +}) + // GET /MinecraftSkins/:username // Paramters: // username: username of character to get skin for diff --git a/package-lock.json b/package-lock.json index f0618d2..4060699 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "busboy": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", + "integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", + "requires": { + "dicer": "0.3.0" + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -156,6 +164,14 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, "download-file-sync": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/download-file-sync/-/download-file-sync-1.0.4.tgz", @@ -218,6 +234,41 @@ "vary": "~1.1.2" } }, + "express-fileupload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.2.0.tgz", + "integrity": "sha512-oe4WpKcSppXnl5peornawWUa6tKmIc1/kJxMNRGJR1A0v4zyLL6VsFR6wZ8P2a4Iq3aGx8xae3Vlr+MOMQhFPw==", + "requires": { + "busboy": "^0.3.1" + } + }, + "express-session": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz", + "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.0", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } + } + }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -296,6 +347,14 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "image-size": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.9.3.tgz", + "integrity": "sha512-5SakFa79uhUVSjKeQE30GVzzLJ0QNzB53+I+/VD1vIesD6GP6uatWIlgU0uisFNLt1u0d6kBydp7yfk+lLJhLQ==", + "requires": { + "queue": "6.0.1" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -354,6 +413,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -362,6 +426,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "parse-cache-control": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", @@ -404,6 +473,19 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "queue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.1.tgz", + "integrity": "sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg==", + "requires": { + "inherits": "~2.0.3" + } + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -492,6 +574,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -562,6 +649,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 6c13c42..9e1504e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,10 @@ "dependencies": { "download-file-sync": "^1.0.4", "express": "^4.17.1", + "express-fileupload": "^1.2.0", + "express-session": "^1.17.1", + "image-size": "^0.9.3", + "node-fetch": "^2.6.1", "sync-request": "^6.1.0" } } diff --git a/static/auth.html b/static/auth.html new file mode 100644 index 0000000..17cfc51 --- /dev/null +++ b/static/auth.html @@ -0,0 +1,40 @@ + + + + Action MC Skins + + + + + + + +
+

ActionMC Skins

+
+ +
+

To authenticate, join this server (works with MC 1.7.x to latest):

+

srv.mc-oauth.net

+

It will give you a six digit code, enter that in the input box below.

+
+ + +
+

Authenticate

+ +
+ +
+
+ +

Codes generated by MC-OAuth expire within 5 minutes of their creation!

+
+
+ + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/static/index.html b/static/index.html index 5f20a18..d85e3fb 100644 --- a/static/index.html +++ b/static/index.html @@ -1,2 +1,43 @@ -

Theta Skin Server

-

Your website goes here!

\ No newline at end of file + + + + Action MC Skins + + + + + + + +
+

ActionMC Skins

+
+ +
+

This page lets you change your skin on our skins server.

+ +

Click the button below to start the authentication procedure.

+ +

Currently, changing your skin is only available to people with premium Minecraft accounts.

+

This is subject to change.

+
+ + +
+

What is a "Premium" Minecraft account?

+ +

A premium Minecraft account is an account that you have PAID for e.g. bought from Mojang/Microsoft.

+
+ +
+

Other Links

+ +

Skin Previewer

+
+ + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/static/skin-previewer.html b/static/skin-previewer.html new file mode 100644 index 0000000..5f20a18 --- /dev/null +++ b/static/skin-previewer.html @@ -0,0 +1,2 @@ +

Theta Skin Server

+

Your website goes here!

\ No newline at end of file diff --git a/static/style-dark.css b/static/style-dark.css new file mode 100644 index 0000000..c8566d5 --- /dev/null +++ b/static/style-dark.css @@ -0,0 +1,76 @@ +body { + margin: 0; + font-family: Arial, Helvetica, sans-serif; + background-image: url("/tile.png"); + background-repeat: repeat; +} + +.body-dark { + background-image: url("/tile_dark.png"); + background-repeat: repeat; +} + +.alert-frame { + padding-top: 2px; + padding-bottom: 2px; + text-align:center; + background-color: #800080; + color: white; + margin: 20px; + width: 90%; + height: 90%; + margin-left: auto; + margin-right: auto; +} + +.invis { + display: none; +} +.title-frame { + padding-top: 10px; + padding-bottom: 10px; + text-align:center; + background-color: #3a3a3a; + color: white; + margin: 50px; + width: 90%; + height: 90%; + margin-left: auto; + margin-right: auto; +} + +.main-frame { + padding-top: 2px; + padding-bottom: 2px; + text-align:center; + background-color: #4a4a4a; + color: white; + margin: 50px; + width: 90%; + height: 90%; + margin-left: auto; + margin-right: auto; +} + +.server-frame { + padding-top: 2px; + padding-bottom: 2px; + text-align:center; + background-color: #606060; + color: white; + margin: 50px; + width: 90%; + height: 90%; + margin-left: auto; + margin-right: auto; +} + +.hidden-frame { + text-align:center; + color: white; + margin: 50px; + width: 90%; + height: 90%; + margin-left: auto; + margin-right: auto; +} \ No newline at end of file diff --git a/static/tile_dark.png b/static/tile_dark.png new file mode 100644 index 0000000..1daa596 Binary files /dev/null and b/static/tile_dark.png differ diff --git a/templates/cape_change.html b/templates/cape_change.html new file mode 100644 index 0000000..8d62cdb --- /dev/null +++ b/templates/cape_change.html @@ -0,0 +1,40 @@ + + + + Action MC Skins + + + + + + +
+

ActionMC Skins

+
+ +
+

Cape Change

+

Please upload the cape you wish to put on your character:

+
+ +
+ +
+

Maximum file size is 16KB (16384 bytes). Skin must be a PNG file.

+

+
+ + + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/templates/failure.html b/templates/failure.html new file mode 100644 index 0000000..023030f --- /dev/null +++ b/templates/failure.html @@ -0,0 +1,35 @@ + + + + Action MC Skins + + + + + + +
+

ActionMC Skins

+
+ +
+

An error occured.

+

The server encountered an error trying to handle your request.

+ +

+
+ + + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/templates/landing.html b/templates/landing.html new file mode 100644 index 0000000..3ad0c71 --- /dev/null +++ b/templates/landing.html @@ -0,0 +1,49 @@ + + + + Action MC Skins + + + + + + +
+

ActionMC Skins

+
+ +
+

Hello, %%USERNAME%%.

+

What do you wish to change?

+ + +
+
+ +

+
+ +
+

These are your current skin and cape textures:

+ + + + + +

Skin

Cape

+

Keep in mind these are the skins we either have cached from Mojang for you or the skin you set using our service.

+
+ + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/templates/size_too_big.html b/templates/size_too_big.html new file mode 100644 index 0000000..fd0ac04 --- /dev/null +++ b/templates/size_too_big.html @@ -0,0 +1,37 @@ + + + + Action MC Skins + + + + + + +
+

ActionMC Skins

+
+ +
+

Image size too big!

+

Skins must be either 64x32 or 64x64 pixels.

+

Capes can only be 64x32 pixels.

+ +

The file you have uploaded was removed from the server.

+

+
+ + + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/templates/skin_change.html b/templates/skin_change.html new file mode 100644 index 0000000..1bdb0e2 --- /dev/null +++ b/templates/skin_change.html @@ -0,0 +1,40 @@ + + + + Action MC Skins + + + + + + +
+

ActionMC Skins

+
+ +
+

Skin Change

+

Please upload the skin you wish to put on your character:

+
+ +
+ +
+

Maximum file size is 16KB (16384 bytes). Skin must be a PNG file.

+

+
+ + + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file diff --git a/templates/success.html b/templates/success.html new file mode 100644 index 0000000..ff893f3 --- /dev/null +++ b/templates/success.html @@ -0,0 +1,35 @@ + + + + Action MC Skins + + + + + + +
+

ActionMC Skins

+
+ +
+

Skin Successfully Changed!

+

You will now see your new skin in-game.

+ +

+
+ + + +
+

Powered by Theta, a custom skin server solution by Virtue.

+
+ + \ No newline at end of file