137 lines
3.7 KiB
JavaScript
Executable File
137 lines
3.7 KiB
JavaScript
Executable File
const {render} = require("pinski/plugins")
|
|
const {getToken} = require("../utils/getuser")
|
|
const constants = require("../utils/constants")
|
|
const db = require("../utils/db")
|
|
const crypto = require("crypto")
|
|
|
|
let users = {}
|
|
let buckets = {}
|
|
|
|
class User {
|
|
constructor(req) {
|
|
this.uuid = crypto.randomUUID()
|
|
this.token = User.getToken(req)
|
|
this.ip = User.getIP(req)
|
|
|
|
users[this.token] = this
|
|
users[this.ip] = this
|
|
}
|
|
|
|
static getToken(req) {
|
|
return getToken(req)
|
|
}
|
|
|
|
static getIP(req) {
|
|
return req.headers["x-forwarded-for"] || req.socket.remoteAddress || null
|
|
}
|
|
}
|
|
|
|
class Bucket {
|
|
constructor() {
|
|
this.left = constants.server_setup.ratelimiting.max_bucket_size,
|
|
this.lastModified = Date.now()
|
|
}
|
|
|
|
drain() {
|
|
// Refill bucket
|
|
this.left += Math.floor(((Date.now() - this.lastModified) / 1000) / constants.server_setup.ratelimiting.bucket_refill_rate_seconds)
|
|
this.left = Math.min(this.left, constants.server_setup.ratelimiting.max_bucket_size)
|
|
|
|
if (this.left > 0) {
|
|
this.left--
|
|
this.lastModified = Date.now()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
function authorized(req, doRateLimit, responseHeaders) {
|
|
if (constants.server_setup.ratelimiting.enabled && doRateLimit) {
|
|
let bucket = module.exports.getBucket(req)
|
|
if (!bucket.drain())
|
|
return { success: false, message: "ratelimited", timeLeftSeconds: ((bucket.lastModified + (1000 * constants.server_setup.ratelimiting.bucket_refill_rate_seconds)) - Date.now()) / 1000 }
|
|
}
|
|
|
|
const token = getToken(req, responseHeaders)
|
|
let out = db.prepare("SELECT * FROM SeenTokens WHERE token = ?").pluck().all(token)
|
|
return { success: Object.keys(req.headers).some((header) => req.headers[header] == "same-origin" || (header == "referer" && req.headers[header].indexOf("/watch?v=") > -1)) || out.length > 0, message: "auth" }
|
|
}
|
|
|
|
// Clear users and buckets that have been inactive
|
|
setInterval(() => {
|
|
for (const k of Object.keys(buckets)) {
|
|
const b = buckets[k]
|
|
|
|
if ((Date.now() - b.lastModified) < 1000 * 60 * 30)
|
|
continue
|
|
|
|
for (const k2 of Object.keys(users)) {
|
|
const u = users[k2]
|
|
if (u.uuid == k)
|
|
delete users[k2]
|
|
}
|
|
delete buckets[k]
|
|
}
|
|
}, 1000 * 60 * 30)
|
|
|
|
module.exports = {
|
|
getUser: function(req) {
|
|
const token = User.getToken(req)
|
|
if (token && users[token]) {
|
|
delete users[User.getIP(req)] // If a user has tokens enabled, we don't want their IP address to block other users on the same network
|
|
return users[token]
|
|
}
|
|
const ip = User.getIP(req)
|
|
if (users[ip])
|
|
return users[ip]
|
|
return new User(req)
|
|
},
|
|
getBucket: function(req) {
|
|
const user = module.exports.getUser(req)
|
|
const bucket = buckets[user.uuid]
|
|
if (bucket)
|
|
return bucket
|
|
buckets[user.uuid] = new Bucket()
|
|
return buckets[user.uuid]
|
|
},
|
|
remove: function(req) {
|
|
const token = User.getToken(req)
|
|
if (token && users[token]) {
|
|
const uuid = users[token].uuid
|
|
delete users[token]
|
|
delete users[User.getIP(req)]
|
|
delete buckets[uuid]
|
|
return
|
|
}
|
|
const ip = User.getIP(req)
|
|
if (users[ip]) {
|
|
const uuid = users[ip].uuid
|
|
delete users[ip]
|
|
delete buckets[uuid]
|
|
}
|
|
},
|
|
|
|
authorize: function(req, doRateLimit, responseHeaders) {
|
|
const output = authorized(req, doRateLimit, responseHeaders)
|
|
if (output.success == false) {
|
|
if (output.message == "ratelimited")
|
|
return render(429, "pug/errors/local-rate-limited.pug", { timeLeftSeconds: output.timeLeftSeconds }, {
|
|
...responseHeaders,
|
|
"Retry-After": Math.ceil(output.timeLeftSeconds)
|
|
})
|
|
else if (output.message == "auth")
|
|
// return render(403, "pug/errors/message-error.pug", { error: new Error("Access denied") }, responseHeaders)
|
|
return {
|
|
statusCode: 403,
|
|
headers: responseHeaders,
|
|
contentType: "application/json",
|
|
content: {
|
|
error: "Access denied"
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|