mirror of
https://github.com/Zandercraft/TechnicFlux.git
synced 2026-10-28 04:29:20 +00:00
Added multi-API-key support and API rate-limiting.
This commit is contained in:
10
.env.example
10
.env.example
@@ -33,3 +33,13 @@ API_KEY=""
|
||||
# This is the name that will be pre-filled when you go to upload custom files for a modpack.
|
||||
# Unset behaviour: You will need to specify the author every time.
|
||||
AUTHOR_NAME="John Doe"
|
||||
|
||||
# (Optional) API Rate-limit Requests
|
||||
# The number of requests that can be made to the API by one IP address within the set window before it will block them.
|
||||
# Default: 1000
|
||||
API_RATE_REQUESTS=1000
|
||||
|
||||
# (Optional) API Rate-limit Window
|
||||
# The amount of time (in milliseconds) that the request limit is applied to.
|
||||
# Default: 60000
|
||||
API_RATE_WINDOW=60000
|
||||
|
||||
4
app.js
4
app.js
@@ -15,7 +15,7 @@ const storage = multer.diskStorage({
|
||||
cb(null, uniquePrefix + '-' + file.originalname)
|
||||
}
|
||||
})
|
||||
const upload = multer({ storage: storage })
|
||||
const upload = multer({ storage })
|
||||
|
||||
// --- Routers ---
|
||||
const indexRouter = require('./routes/index')
|
||||
@@ -49,7 +49,7 @@ app.use(function (err, req, res, next) {
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500)
|
||||
res.render('error', {title: `Error ${err.status}: ${err.message}`})
|
||||
res.render('error', { title: `Error ${err.status}: ${err.message}` })
|
||||
})
|
||||
|
||||
module.exports.app = app
|
||||
|
||||
101
database.js
101
database.js
@@ -16,6 +16,13 @@ const userSchema = new Schema({
|
||||
login_history: [{ timestamp: Date, userAgent: String }]
|
||||
})
|
||||
|
||||
const apiKeySchema = new Schema({
|
||||
owner: { type: SchemaTypes.ObjectId, ref: 'technicflux_users' },
|
||||
key: String,
|
||||
name: String,
|
||||
created_at: Date
|
||||
})
|
||||
|
||||
const modpackSchema = new Schema({
|
||||
name: String,
|
||||
display_name: String,
|
||||
@@ -55,6 +62,7 @@ const buildSchema = new Schema({
|
||||
|
||||
// --- Models ---
|
||||
const User = mongoose.model('technicflux_users', userSchema)
|
||||
const APIKey = mongoose.model('technicflux_keys', apiKeySchema)
|
||||
const Modpack = mongoose.model('technicflux_modpacks', modpackSchema)
|
||||
const Mod = mongoose.model('technicflux_mods', modSchema)
|
||||
const Build = mongoose.model('technicflux_builds', buildSchema)
|
||||
@@ -119,7 +127,7 @@ exports.getUserById = (uId) => {
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the user with the provided email.
|
||||
* Gets the user with the provided username.
|
||||
*/
|
||||
exports.getUserByUsername = (uUsername) => {
|
||||
// Fetch information about the user with the given username
|
||||
@@ -155,7 +163,7 @@ exports.authUser = (uUsername, uPassword, logHistory = false, uAgent = 'Unknown'
|
||||
// Fetch information about the user with the given username
|
||||
return User.findOne({ username: uUsername }).exec().then((user) => {
|
||||
// User found. Check password.
|
||||
return bcrypt.compare(uPassword, user.password).then((isMatch) => {
|
||||
return bcrypt.compare(uPassword, user.password).then(async (isMatch) => {
|
||||
// Check if it is a match
|
||||
if (isMatch) {
|
||||
// Update the user's login history.
|
||||
@@ -163,7 +171,7 @@ exports.authUser = (uUsername, uPassword, logHistory = false, uAgent = 'Unknown'
|
||||
const newLogin = { timestamp: new Date(), userAgent: String(uAgent) }
|
||||
|
||||
// Add the new login to the user's login history
|
||||
return User.updateOne(
|
||||
return await User.updateOne(
|
||||
{ username: uUsername },
|
||||
{ $push: { login_history: newLogin } }
|
||||
).exec().then(() => {
|
||||
@@ -229,6 +237,91 @@ exports.updateUser = (uUsername, object) => {
|
||||
})
|
||||
}
|
||||
|
||||
// --- API-Key-Related Functions
|
||||
|
||||
/*
|
||||
* Creates a new api key with the given information.
|
||||
*/
|
||||
exports.createAPIKey = (kOwner, kKey, kName) => {
|
||||
// Encrypt the password using bcrypt and save user
|
||||
return bcrypt.genSalt(10).then((Salt) => {
|
||||
return bcrypt.hash(kKey, Salt).then((hash) => {
|
||||
// Create an APIKey object
|
||||
const newKey = new APIKey({
|
||||
owner: kOwner,
|
||||
key: hash,
|
||||
name: kName,
|
||||
created_at: new Date()
|
||||
})
|
||||
|
||||
// Commit it to the database
|
||||
return newKey.save().then((key) => {
|
||||
process.stdout.write(`Successfully added new API key '${kName}' for ${kOwner}\n`)
|
||||
return key
|
||||
}).catch((reason) => {
|
||||
process.stdout.write(`ERROR (when saving new API key): ${reason}\n`)
|
||||
return false
|
||||
})
|
||||
}).catch((err) => {
|
||||
// Issue creating hashed api key
|
||||
process.stdout.write(`ERROR (while hashing key): ${err}`)
|
||||
return false
|
||||
})
|
||||
}).catch((err) => {
|
||||
// Issue creating hashed object salt
|
||||
process.stdout.write(`ERROR (while generating api key salt): ${err}`)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the corresponding password matches that of the provided username.
|
||||
*/
|
||||
exports.authAPIKey = (uKey) => {
|
||||
// Fetch information about the user with the given username
|
||||
return APIKey.find({ }).exec().then(async (keys) => {
|
||||
// Check if any of the keys match.
|
||||
let valid = [false, undefined]
|
||||
for (const key of keys) {
|
||||
await bcrypt.compare(uKey, key.key).then((isMatch) => {
|
||||
// Check if it is a match
|
||||
if (isMatch) {
|
||||
// The key matched.
|
||||
valid = [true, key]
|
||||
}
|
||||
})
|
||||
}
|
||||
return valid
|
||||
}).catch((reason) => {
|
||||
console.log(reason)
|
||||
// DB error
|
||||
debug('ERROR (DB): Could not fetch API keys.')
|
||||
return [false, undefined]
|
||||
})
|
||||
}
|
||||
|
||||
exports.getAPIKeysByOwner = (mOwner) => {
|
||||
return APIKey.find({ owner: mOwner }).populate('owner').exec().then((keys) => {
|
||||
// API Keys found.
|
||||
return keys
|
||||
}).catch((reason) => {
|
||||
// No API keys found for this owner.
|
||||
debug(`ERROR (DB): Failed to find any API keys with this owner because of: ${reason}`)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
exports.deleteAPIKey = (kId) => {
|
||||
return APIKey.deleteOne({ _id: kId }).exec().then(() => {
|
||||
// Successfully deleted this api key
|
||||
return true
|
||||
}).catch((reason) => {
|
||||
// Failed to delete the api key
|
||||
debug(`ERROR (DB): Failed to delete API Key '${kId}' because of: ${reason}`)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// --- Modpack-Related Functions ---
|
||||
exports.createModpack = (mSlug, mDisplayName, mOwner) => {
|
||||
// Create a modpack object
|
||||
@@ -335,7 +428,7 @@ exports.getModBySlug = (mSlug) => {
|
||||
author: mods[0].author,
|
||||
description: mods[0].description,
|
||||
link: mods[0].link,
|
||||
versions: mods.map((modVersion) => modVersion['version'])
|
||||
versions: mods.map((modVersion) => modVersion.version)
|
||||
}
|
||||
}
|
||||
}).catch((reason) => {
|
||||
|
||||
115
package-lock.json
generated
115
package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"mongoose": "^7.5.3",
|
||||
"morgan": "~1.9.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"rate-limit-mongo": "^2.3.2",
|
||||
"standard": "^17.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -439,6 +440,15 @@
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
|
||||
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
@@ -801,6 +811,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
||||
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
@@ -3077,6 +3095,17 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/optional-require": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz",
|
||||
"integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==",
|
||||
"dependencies": {
|
||||
"require-at": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||
@@ -3367,6 +3396,62 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limit-mongo": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/rate-limit-mongo/-/rate-limit-mongo-2.3.2.tgz",
|
||||
"integrity": "sha512-dLck0j5N/AX9ycVHn5lX9Ti2Wrrwi1LfbXitu/mMBZOo2nC26RgYKJVbcb2mYgb9VMaPI2IwJVzIa2hAQrMaDA==",
|
||||
"dependencies": {
|
||||
"mongodb": "^3.6.7",
|
||||
"twostep": "0.4.2",
|
||||
"underscore": "1.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limit-mongo/node_modules/bson": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
|
||||
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limit-mongo/node_modules/mongodb": {
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
|
||||
"integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
|
||||
"dependencies": {
|
||||
"bl": "^2.2.1",
|
||||
"bson": "^1.1.4",
|
||||
"denque": "^1.4.1",
|
||||
"optional-require": "^1.1.8",
|
||||
"safe-buffer": "^5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"saslprep": "^1.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"aws4": {
|
||||
"optional": true
|
||||
},
|
||||
"bson-ext": {
|
||||
"optional": true
|
||||
},
|
||||
"kerberos": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-client-encryption": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-extjson": {
|
||||
"optional": true
|
||||
},
|
||||
"snappy": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
@@ -3492,6 +3577,14 @@
|
||||
"url": "https://github.com/sponsors/mysticatea"
|
||||
}
|
||||
},
|
||||
"node_modules/require-at": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz",
|
||||
"integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.6",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
|
||||
@@ -3609,6 +3702,18 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
@@ -4105,6 +4210,11 @@
|
||||
"strip-bom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/twostep": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/twostep/-/twostep-0.4.2.tgz",
|
||||
"integrity": "sha512-O/wdPYk9ey04qcCiw8AQN74DbvLFZLAgnryrNTpV7T/sxB4lcGkCMHynx5xCcA6fCh739ZAqp3HcGhy770X1qA=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -4231,6 +4341,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
|
||||
"integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw=="
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "set DEBUG=technicflux:* && node ./bin/www"
|
||||
"start": "set DEBUG=technicflux:* && node ./bin/www",
|
||||
"lint": "standard",
|
||||
"lint-and-fix": "standard --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "^2.7.29",
|
||||
@@ -48,6 +50,7 @@
|
||||
"mongoose": "^7.5.3",
|
||||
"morgan": "~1.9.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"rate-limit-mongo": "^2.3.2",
|
||||
"standard": "^17.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
158
routes/api.js
158
routes/api.js
@@ -1,9 +1,44 @@
|
||||
const express = require('express')
|
||||
const pckg = require('../package.json')
|
||||
const branchName = require('current-git-branch')() || "release"
|
||||
const rateLimit = require('express-rate-limit')
|
||||
const MongoStore = require('rate-limit-mongo')
|
||||
const database = require('../database')
|
||||
const branchName = require('current-git-branch')() || 'release'
|
||||
const pckg = require('../package.json')
|
||||
const router = express.Router()
|
||||
|
||||
// --- Rate Limiter ---
|
||||
const maxRequests = process.env.API_RATE_REQUESTS || 1000
|
||||
const rateWindow = process.env.API_RATE_WINDOW || 60000
|
||||
|
||||
const limiter = rateLimit({
|
||||
store: new MongoStore({
|
||||
uri: process.env.MONGODB_CONN_STRING,
|
||||
collectionName: 'technicflux_ratelimit',
|
||||
expireTimeMs: rateLimit,
|
||||
errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
||||
}),
|
||||
max: maxRequests,
|
||||
windowMs: rateWindow,
|
||||
legacyHeaders: true,
|
||||
message: { error: 'Too many requests from this IP. You are being rate-limited.' }
|
||||
})
|
||||
router.use(limiter)
|
||||
|
||||
// --- Module Error Functions ---
|
||||
|
||||
function error500 (res) {
|
||||
return res.status(500).json({
|
||||
error: 'Internal Server Error'
|
||||
})
|
||||
}
|
||||
|
||||
// --- API Routes ---
|
||||
|
||||
/**
|
||||
* Base API Route : /api/
|
||||
* Possible Statuses: 200
|
||||
* Purpose: Displays information about the TechnicFlux Solder API.
|
||||
*/
|
||||
router.get('/', (req, res) => {
|
||||
return res.json({
|
||||
api: pckg.prettyName,
|
||||
@@ -12,27 +47,32 @@ router.get('/', (req, res) => {
|
||||
meta: {
|
||||
description: "TechnicFlux implementation of Technic's Solder API for retrieval of modpack and mod info.",
|
||||
license: `https://github.com/Zandercraft/TechnicFlux/blob/${branchName}/LICENSE.txt`,
|
||||
repo: "https://github.com/Zandercraft/TechnicFlux",
|
||||
documentation: "https://github.com/Zandercraft/TechnicFlux/wiki",
|
||||
repo: 'https://github.com/Zandercraft/TechnicFlux',
|
||||
documentation: 'https://github.com/Zandercraft/TechnicFlux/wiki',
|
||||
attribution: {
|
||||
name: "Zandercraft",
|
||||
github: "https://github.com/Zandercraft",
|
||||
website: "https://zandercraft.ca",
|
||||
name: 'Zandercraft',
|
||||
github: 'https://github.com/Zandercraft',
|
||||
website: 'https://zandercraft.ca'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Mod API Route : /api/mod/:modname/:modversion?
|
||||
* Possible Statuses: 200, 404, 500
|
||||
* Purpose: Displays information about a mod or a specific version of that mod.
|
||||
*/
|
||||
router.get('/mod/:modname/:modversion?', (req, res) => {
|
||||
let modName = req.params.modname
|
||||
let modVersion = req.params?.modversion
|
||||
const modName = req.params.modname
|
||||
const modVersion = req.params?.modversion
|
||||
|
||||
return database.getModBySlug(`${modName}`).then((mod) => {
|
||||
// Check if a mod was found
|
||||
if (mod === null) {
|
||||
// Mod Not Found
|
||||
return res.status(404).json({
|
||||
"error": "Mod does not exist"
|
||||
error: 'Mod does not exist'
|
||||
})
|
||||
} else if (modVersion === undefined) {
|
||||
// Request is for the mod's info.
|
||||
@@ -43,7 +83,7 @@ router.get('/mod/:modname/:modversion?', (req, res) => {
|
||||
if (version === null) {
|
||||
// Version Not Found
|
||||
return res.status(404).json({
|
||||
"error": "Mod version does not exist"
|
||||
error: 'Mod version does not exist'
|
||||
})
|
||||
} else {
|
||||
// Found mod version
|
||||
@@ -56,46 +96,52 @@ router.get('/mod/:modname/:modversion?', (req, res) => {
|
||||
}
|
||||
}).catch(() => {
|
||||
// Issue with db request
|
||||
return res.status(500).json({
|
||||
"error": "Internal Server Error"
|
||||
})
|
||||
return error500(res)
|
||||
})
|
||||
}
|
||||
}).catch(() => {
|
||||
// Issue with db request
|
||||
return res.status(500).json({
|
||||
"error": "Internal Server Error"
|
||||
})
|
||||
return error500(res)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Modpack Index API Route : /api/modpack/
|
||||
* Possible Statuses: 200, 500
|
||||
* Purpose: Displays a list of all modpacks that are on this TechnicFlux instance.
|
||||
*/
|
||||
router.get('/modpack', (req, res) => {
|
||||
return database.getAllModpacks().then((modpacks) => {
|
||||
// Modpack Index
|
||||
return res.json({
|
||||
modpacks: (modpacks === null) ? {} : modpacks.reduce((obj, item) => {
|
||||
obj[item['name']] = String(item['display_name'])
|
||||
return obj
|
||||
}, {}),
|
||||
modpacks: (modpacks === null)
|
||||
? {}
|
||||
: modpacks.reduce((obj, item) => {
|
||||
obj[item.name] = String(item.display_name)
|
||||
return obj
|
||||
}, {}),
|
||||
mirror_url: `http://${process.env.HOST}/mods/`
|
||||
})
|
||||
}).catch(() => {
|
||||
// Issue with db request
|
||||
return res.status(500).json({
|
||||
"error": "Internal Server Error"
|
||||
})
|
||||
return error500(res)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Modpack API Route : /api/modpack/:modpack/:build?
|
||||
* Possible Statuses: 200, 404, 500
|
||||
* Purpose: Displays information about a modpack or a specific build of that modpack.
|
||||
*/
|
||||
router.get('/modpack/:slug/:build?', (req, res) => {
|
||||
let slug = req.params.slug
|
||||
let build = req.params?.build
|
||||
const slug = req.params.slug
|
||||
const build = req.params?.build
|
||||
|
||||
return database.getModpackBySlug(`${slug}`).then((modpack) => {
|
||||
if (modpack === null) {
|
||||
// Modpack Not Found
|
||||
return res.status(404).json({
|
||||
"error": "Modpack does not exist"
|
||||
error: 'Modpack does not exist'
|
||||
})
|
||||
} else if (build === undefined) {
|
||||
// Modpack info
|
||||
@@ -104,7 +150,7 @@ router.get('/modpack/:slug/:build?', (req, res) => {
|
||||
display_name: modpack.display_name,
|
||||
recommended: modpack.recommended,
|
||||
latest: modpack.latest,
|
||||
builds: modpack.builds.map((build) => build['version'])
|
||||
builds: modpack.builds.map((build) => build.version)
|
||||
})
|
||||
} else {
|
||||
// Modpack build info
|
||||
@@ -133,38 +179,54 @@ router.get('/modpack/:slug/:build?', (req, res) => {
|
||||
}
|
||||
}).catch(() => {
|
||||
// Issue with db request
|
||||
return res.status(500).json({
|
||||
"error": "Internal Server Error"
|
||||
})
|
||||
return error500(res)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* API Key Verification Route : /api/verify/:key?
|
||||
* Possible Statuses: 200, 400, 403
|
||||
* Purpose: Checks if the given API key is valid with this instance of TechnicFlux
|
||||
*/
|
||||
router.get('/verify/:key?', (req, res) => {
|
||||
let api_key = req.params?.key
|
||||
const apiKey = req.params?.key
|
||||
|
||||
if (api_key !== undefined) {
|
||||
if (api_key === process.env?.API_KEY)
|
||||
// Key validated
|
||||
return res.json({
|
||||
valid: 'Key validated.',
|
||||
name: 'API KEY',
|
||||
created_at: 'A long time ago'
|
||||
})
|
||||
else {
|
||||
// Invalid key provided
|
||||
return res.json({
|
||||
error: 'Invalid key provided.'
|
||||
})
|
||||
}
|
||||
if (apiKey !== undefined) {
|
||||
database.authAPIKey(apiKey).then((key) => {
|
||||
if (apiKey === process.env?.API_KEY) {
|
||||
// Key validated
|
||||
return res.json({
|
||||
valid: 'Key validated.',
|
||||
name: 'API KEY',
|
||||
created_at: 'A long time ago'
|
||||
})
|
||||
} else if (key[0] === true) {
|
||||
// Key validated
|
||||
return res.json({
|
||||
valid: 'Key validated.',
|
||||
name: key[1].name,
|
||||
created_at: key[1].created_at
|
||||
})
|
||||
} else {
|
||||
// Invalid key provided
|
||||
return res.status(403).json({
|
||||
error: 'Invalid key provided.'
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// No key provided
|
||||
return res.json({
|
||||
return res.status(400).json({
|
||||
error: 'No API key provided.'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/* Invalid API Routes */
|
||||
/**
|
||||
* Invalid API Routes : /api/<invalid route>
|
||||
* Possible Statuses: 405
|
||||
* Purpose: Displays an error when an invalid route is called.
|
||||
*/
|
||||
router.all('*', (req, res) => {
|
||||
return res.status(405).json({
|
||||
code: res.statusCode,
|
||||
|
||||
Reference in New Issue
Block a user