@bsv/overlay-express
Version:
BSV Blockchain Overlay Express
118 lines • 4.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BanService = void 0;
/**
* BanService provides a persistent ban list stored in MongoDB.
*
* When the Janitor or an admin removes a SHIP/SLAP token, the associated
* domain or outpoint can be added to the ban list. The BanAwareLookupWrapper
* checks this list before admitting new outputs, preventing GASP from
* re-syncing previously removed stale tokens.
*/
class BanService {
constructor(db) {
this.bans = db.collection('bannedRecords');
}
/**
* Sanitizes a value for use in MongoDB queries, preventing NoSQL injection.
* Ensures the value is a plain string and rejects objects/arrays that could
* contain MongoDB operators like $ne, $gt, etc.
*/
sanitize(value) {
if (typeof value !== 'string') {
throw new TypeError('Invalid input: expected a string value');
}
return value;
}
/**
* Creates indexes for efficient ban lookups.
*/
async ensureIndexes() {
await this.bans.createIndex({ type: 1, value: 1 }, { unique: true });
await this.bans.createIndex({ bannedAt: -1 });
}
/**
* Bans a domain, preventing any SHIP/SLAP tokens referencing it from being stored.
*/
async banDomain(domain, reason, bannedBy) {
const safeDomain = this.sanitize(domain);
await this.bans.updateOne({ type: 'domain', value: safeDomain }, {
$set: {
type: 'domain',
value: safeDomain,
reason: reason !== null && reason !== void 0 ? reason : 'Manually banned',
bannedAt: new Date(),
bannedBy
}
}, { upsert: true });
}
/**
* Removes a domain ban.
*/
async unbanDomain(domain) {
await this.bans.deleteOne({ type: 'domain', value: this.sanitize(domain) });
}
/**
* Checks if a domain is banned.
*/
async isDomainBanned(domain) {
const record = await this.bans.findOne({ type: 'domain', value: this.sanitize(domain) });
return record !== null;
}
/**
* Bans a specific outpoint (txid.outputIndex), preventing it from being re-admitted.
*/
async banOutpoint(txid, outputIndex, reason, domain, bannedBy) {
const value = `${this.sanitize(txid)}.${Number(outputIndex)}`;
await this.bans.updateOne({ type: 'outpoint', value }, {
$set: {
type: 'outpoint',
value,
domain: domain != null ? this.sanitize(domain) : undefined,
reason: reason !== null && reason !== void 0 ? reason : 'Manually banned',
bannedAt: new Date(),
bannedBy
}
}, { upsert: true });
}
/**
* Removes an outpoint ban.
*/
async unbanOutpoint(txid, outputIndex) {
const value = `${this.sanitize(txid)}.${Number(outputIndex)}`;
await this.bans.deleteOne({ type: 'outpoint', value });
}
/**
* Checks if a specific outpoint is banned.
*/
async isOutpointBanned(txid, outputIndex) {
const value = `${this.sanitize(txid)}.${Number(outputIndex)}`;
const record = await this.bans.findOne({ type: 'outpoint', value });
return record !== null;
}
/**
* Lists all bans, optionally filtered by type.
*/
async listBans(type) {
const query = typeof type === 'string' ? { type } : {};
return await this.bans.find(query).sort({ bannedAt: -1 }).toArray();
}
/**
* Removes a ban by type and value.
*/
async removeBan(type, value) {
await this.bans.deleteOne({ type: this.sanitize(type), value: this.sanitize(value) });
}
/**
* Returns ban statistics.
*/
async getStats() {
const [domainBans, outpointBans] = await Promise.all([
this.bans.countDocuments({ type: 'domain' }),
this.bans.countDocuments({ type: 'outpoint' })
]);
return { domainBans, outpointBans, totalBans: domainBans + outpointBans };
}
}
exports.BanService = BanService;
//# sourceMappingURL=BanService.js.map