mirror of
https://github.com/Zandercraft/TechnicFlux.git
synced 2026-10-29 04:39:20 +00:00
Added multi-API-key support and API rate-limiting.
This commit is contained in:
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