Skip to content

Commit

Permalink
Added multi-API-key support and API rate-limiting.
Browse files Browse the repository at this point in the history
  • Loading branch information
ZandercraftGames committed Sep 29, 2023
1 parent b2f216d commit f475633
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 55 deletions.
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Binary file modified README.md
Binary file not shown.
4 changes: 2 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down
101 changes: 97 additions & 4 deletions database.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Check warning on line 68 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant Build
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -155,15 +163,15 @@ 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) => {

Check notice on line 164 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Signature mismatch

Invalid number of arguments, expected 1
// 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.
if (logHistory) {
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(() => {

Check notice on line 177 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Signature mismatch

Invalid number of arguments, expected 1
Expand Down Expand Up @@ -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) => {

Check warning on line 258 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Void function return value used

Void function return value is used
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 notice on line 282 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Signature mismatch

Invalid number of arguments, expected 1
// 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) => {

Check notice on line 304 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Signature mismatch

Invalid number of arguments, expected 1
// 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(() => {

Check notice on line 315 in database.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

Signature mismatch

Invalid number of arguments, expected 1
// 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
Expand Down Expand Up @@ -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) => {
Expand Down
115 changes: 115 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
Loading

0 comments on commit f475633

Please sign in to comment.