@yveskaufmann/koa2-ratelimit
Version:
IP rate-limiting middleware for Koajs 2. Use to limit repeated requests to APIs and/or endpoints such as password reset.
168 lines • 5.72 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongodbStore = void 0;
const mongoose_1 = __importDefault(require("mongoose"));
const Time_1 = require("../Time");
const Store_1 = require("./Store");
function findOrCreate({ where, defaults }) {
return __awaiter(this, void 0, void 0, function* () {
return this.collection.findOneAndUpdate(where, { $setOnInsert: defaults }, { upsert: true, returnDocument: "after" } // return new doc if one is upserted
);
});
}
const abuseSchema = new mongoose_1.default.Schema({
key: {
type: String,
required: true,
index: { unique: true },
},
counter: {
type: Number,
required: true,
default: 0,
},
dateEnd: {
type: Date,
required: true,
},
});
abuseSchema.statics.findOrCreate = findOrCreate;
const abuseHistorySchema = new mongoose_1.default.Schema({
key: {
type: String,
required: true,
},
prefix: {
type: String,
required: false,
},
interval: {
type: Number,
required: true,
},
nbMax: {
type: Number,
required: true,
},
nbHit: {
type: Number,
required: true,
default: 0,
},
userId: {
type: Number,
required: false,
},
ip: {
type: String,
required: false,
},
dateEnd: {
type: Date,
required: true,
},
createdAt: {
type: Date,
required: true,
default: Date.now,
},
updatedAt: {
type: Date,
required: true,
default: Date.now,
},
});
abuseHistorySchema.index({ key: 1, dateEnd: 1 }, { unique: true });
function beforSave(next) {
this.updatedAt = Date.now();
next();
}
abuseHistorySchema.pre("save", beforSave);
abuseHistorySchema.pre("update", beforSave);
abuseHistorySchema.pre("findOneAndUpdate", beforSave);
abuseHistorySchema.statics.findOrCreate = findOrCreate;
class MongodbStore extends Store_1.Store {
constructor(mongodb, options = {}) {
super();
this.mongodb = mongodb;
this.collectionName = options.collectionName || "Ratelimits";
this.collectionAbuseName =
options.collectionAbuseName || `${this.collectionName}Abuses`;
this.Ratelimits = mongodb.model(this.collectionName, abuseSchema);
this.Abuse = mongodb.model(this.collectionAbuseName, abuseHistorySchema);
}
_increment(model, where, nb = 1, field) {
return __awaiter(this, void 0, void 0, function* () {
return model.findOneAndUpdate(where, { $inc: { [field]: nb } });
});
}
// remove all if time is passed
_removeAll() {
return __awaiter(this, void 0, void 0, function* () {
yield this.Ratelimits.deleteMany({ dateEnd: { $lte: Date.now() } });
});
}
incr(key, options, weight) {
return __awaiter(this, void 0, void 0, function* () {
yield this._removeAll();
const data = yield this.Ratelimits.findOrCreate({
where: { key },
defaults: {
key,
dateEnd: Date.now() + Time_1.Time.toMs(options.interval),
counter: 0,
},
});
yield this._increment(this.Ratelimits, { key }, weight, "counter");
return {
counter: data.value.counter + weight,
dateEnd: data.value.dateEnd,
};
});
}
decrement(key, options, weight) {
return __awaiter(this, void 0, void 0, function* () {
yield this._increment(this.Ratelimits, { key }, -weight, "counter");
});
}
saveAbuse(options) {
return __awaiter(this, void 0, void 0, function* () {
const ratelimit = yield this.Ratelimits.findOne({
key: options.key,
}).exec();
if (ratelimit) {
// eslint-disable-next-line
const dateEnd = ratelimit.dateEnd;
// create if not exist
yield this.Abuse.findOrCreate({
where: { key: options.key, dateEnd },
defaults: {
key: options.key,
prefix: options.prefixKey,
interval: options.interval,
nbMax: options.max,
nbHit: options.max,
userId: options.user_id,
ip: options.ip,
dateEnd,
},
}).catch(() => { });
yield this._increment(this.Abuse, { key: options.key, dateEnd }, 1, "nbHit");
}
});
}
}
exports.MongodbStore = MongodbStore;
//# sourceMappingURL=MongodbStore.js.map