I forget
This commit is contained in:
parent
bc7c15271e
commit
2c851516b2
12 changed files with 83 additions and 63 deletions
|
@ -19,7 +19,7 @@ export default async function buildDBClient(): Promise<Db> {
|
|||
}
|
||||
}
|
||||
|
||||
const redis = Redis.createClient({ url: process.env.REDIS_URL });
|
||||
const redis = Redis.createClient({ url: process.env['REDIS_URL'] });
|
||||
|
||||
export { redis };
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import buildDBClient, { redis } from './db';
|
|||
|
||||
config();
|
||||
|
||||
const PORT = Number(process.env.API_PORT || 9000);
|
||||
const PORT = Number(process.env['API_PORT'] || 9000);
|
||||
const DEBUG = process.env.NODE_ENV != 'production';
|
||||
const SESSION_LIFETIME = 1000 * 60 * 60 * 24 * 7;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Request, Response } from "express";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { app } from "..";
|
||||
|
||||
app.use('*', (req: Request, res: Response, next: () => void) => {
|
||||
app.use('*', (req: Request, res: Response, next: NextFunction) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, x-auth-user, x-auth-token');
|
||||
res.header('Access-Control-Allow-Methods', '*');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Request, Response } from "express";
|
||||
import { Request } from "express";
|
||||
import { app, logger } from "..";
|
||||
|
||||
app.use('*', (req: Request, res: Response, next: () => void) => {
|
||||
app.use('*', (req: Request, next: () => void) => {
|
||||
logger.debug(`${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Request, Response } from "express";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { ulid } from "ulid";
|
||||
import { app, logger } from "..";
|
||||
import { redis } from "../db";
|
||||
|
@ -14,34 +14,37 @@ class RateLimiter {
|
|||
this.timeframe = limits.timeframe;
|
||||
}
|
||||
|
||||
async execute(req: Request, res: Response, next: () => void) {
|
||||
try {
|
||||
const ip = req.ip;
|
||||
const reqId = ulid();
|
||||
|
||||
// ratelimit:ip_address_base64:route_base64
|
||||
const redisKey = `ratelimit:${Buffer.from(ip).toString('base64')}:${Buffer.from(this.route).toString('base64')}`;
|
||||
|
||||
const reqs = await redis.SCARD(redisKey);
|
||||
|
||||
if (reqs >= this.limit) {
|
||||
logger.debug(`Ratelimiter: IP address exceeded ratelimit for ${this.route} [${this.limit}/${this.timeframe}]`);
|
||||
res
|
||||
.status(429)
|
||||
.send({
|
||||
error: 'You are being rate limited.',
|
||||
limit: this.limit,
|
||||
timeframe: this.timeframe,
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
await redis.SADD(redisKey, reqId);
|
||||
await redis.sendCommand([ 'EXPIREMEMBER', redisKey, reqId, this.timeframe.toString() ]);
|
||||
middleware() {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const ip = req.ip;
|
||||
const reqId = ulid();
|
||||
// ratelimit:ip_address_base64:route_base64
|
||||
const redisKey = `ratelimit:${Buffer.from(ip).toString('base64')}:${Buffer.from(this.route).toString('base64')}`;
|
||||
const reqs = await redis.SCARD(redisKey);
|
||||
if (reqs >= this.limit) {
|
||||
logger.debug(`Ratelimiter: IP address exceeded ratelimit for ${this.route} [${this.limit}/${this.timeframe}]`);
|
||||
res
|
||||
.status(429)
|
||||
.send({
|
||||
error: 'You are being rate limited.',
|
||||
limit: this.limit,
|
||||
timeframe: this.timeframe,
|
||||
});
|
||||
} else {
|
||||
await redis.SADD(redisKey, reqId);
|
||||
await redis.sendCommand([ 'EXPIREMEMBER', redisKey, reqId, this.timeframe.toString() ]);
|
||||
next();
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
next(e);
|
||||
}
|
||||
} catch(e) { console.error(e) }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
app.use('*', (...args) => (new RateLimiter('*', { limit: 20, timeframe: 1 })).execute(...args));
|
||||
const globalRateLimiter = new RateLimiter('*', { limit: 20, timeframe: 1 });
|
||||
app.use('*', globalRateLimiter.middleware());
|
||||
|
||||
export { RateLimiter }
|
||||
export { RateLimiter };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Request, Response } from "express";
|
||||
import { Request } from "express";
|
||||
import { Collection, Db } from "mongodb";
|
||||
import { app, SESSION_LIFETIME } from "..";
|
||||
|
||||
|
@ -8,7 +8,7 @@ export function initializeSessionsMiddleware(db: Db) {
|
|||
sessionsCollection = db.collection('sessions');
|
||||
}
|
||||
|
||||
app.use('*', async (req: Request, res: Response, next: () => void) => {
|
||||
app.use('*', async (req: Request, next: () => void) => {
|
||||
next();
|
||||
const user = req.header('x-auth-user');
|
||||
const token = req.header('x-auth-token');
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { app, db } from '../..';
|
||||
import { app } from '../..';
|
||||
import { Request, Response } from 'express';
|
||||
import { badRequest, ensureObjectStructure, isAuthenticated, requireAuth, unauthorized } from '../../utils';
|
||||
import { botReq } from '../internal/ws';
|
||||
import { Collection, Db, ObjectId } from 'mongodb';
|
||||
import { Collection, Db } from 'mongodb';
|
||||
import { ulid } from 'ulid';
|
||||
|
||||
let serversCollection: Collection;
|
||||
|
@ -32,15 +32,15 @@ app.get('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (r
|
|||
return res.status(response.statusCode ?? 500).send({ error: response.error });
|
||||
}
|
||||
|
||||
if (!response.server) return res.status(404).send({ error: 'Server not found' });
|
||||
if (!response['server']) return res.status(404).send({ error: 'Server not found' });
|
||||
|
||||
const permissionLevel: 0|1|2|3 = response.perms;
|
||||
const permissionLevel: 0|1|2|3 = response['perms'];
|
||||
if (permissionLevel < 1) return unauthorized(res, `Only moderators and bot managers may view this.`);
|
||||
|
||||
const serverConfig = await serversCollection.findOne({ id: server });
|
||||
|
||||
const result = {
|
||||
antispam: (serverConfig?.automodSettings?.spam as AntispamRule[]|undefined)
|
||||
antispam: (serverConfig?.['automodSettings']?.spam as AntispamRule[]|undefined)
|
||||
?.map(r => ({
|
||||
action: r.action,
|
||||
channels: r.channels,
|
||||
|
@ -64,7 +64,7 @@ app.patch('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 })
|
|||
if (!server || !ruleid) return badRequest(res);
|
||||
|
||||
const serverConfig = await serversCollection.findOne({ id: server });
|
||||
const antiSpamRules: AntispamRule[] = serverConfig?.automodSettings?.spam ?? [];
|
||||
const antiSpamRules: AntispamRule[] = serverConfig?.['automodSettings']?.spam ?? [];
|
||||
|
||||
const rule = antiSpamRules.find(r => r.id == ruleid);
|
||||
if (!rule) return res.status(404).send({ error: 'No rule with this ID could be found.' });
|
||||
|
@ -100,7 +100,7 @@ app.post('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (
|
|||
return res.status(response.statusCode ?? 500).send({ error: response.error });
|
||||
}
|
||||
|
||||
if (!response.server) return res.status(404).send({ error: 'Server not found' });
|
||||
if (!response['server']) return res.status(404).send({ error: 'Server not found' });
|
||||
|
||||
let rule: any;
|
||||
try {
|
||||
|
@ -146,7 +146,7 @@ app.delete('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 }
|
|||
return res.status(response.statusCode ?? 500).send({ error: response.error });
|
||||
}
|
||||
|
||||
if (!response.server) return res.status(404).send({ error: 'Server not found' });
|
||||
if (!response['server']) return res.status(404).send({ error: 'Server not found' });
|
||||
|
||||
let result;
|
||||
try {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { EventEmitter } from 'events';
|
|||
import { logger } from "../..";
|
||||
import server from '../../server';
|
||||
|
||||
if (!process.env.BOT_API_TOKEN) {
|
||||
if (!process.env['BOT_API_TOKEN']) {
|
||||
logger.error(`$BOT_API_TOKEN is not set. This token is `
|
||||
+ `required for the bot to communicate with the API.`);
|
||||
process.exit(1);
|
||||
|
|
|
@ -36,7 +36,7 @@ app.post('/login/begin',
|
|||
if (!body.user || typeof body.user != 'string') return badRequest(res);
|
||||
const r = await botReq('requestLogin', { user: body.user.toLowerCase() });
|
||||
if (!r.success) return res.status(r.statusCode ?? 500).send(JSON.stringify({ error: r.error }, null, 4));
|
||||
res.status(200).send({ success: true, nonce: r.nonce, code: r.code, uid: r.uid });
|
||||
res.status(200).send({ success: true, nonce: r['nonce'], code: r['code'], uid: r['uid'] });
|
||||
});
|
||||
|
||||
app.post('/login/complete',
|
||||
|
@ -57,7 +57,7 @@ app.post('/login/complete',
|
|||
});
|
||||
|
||||
if (!loginAttempt) return res.status(404).send({ error: 'The provided login info could not be found.' });
|
||||
if (!loginAttempt.confirmed) {
|
||||
if (!loginAttempt['confirmed']) {
|
||||
return res.status(400).send({ error: "This code is not yet valid." });
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { app, db, logger } from '..';
|
||||
import { Request, Response } from 'express';
|
||||
import { Response } from 'express';
|
||||
import { botReq } from './internal/ws';
|
||||
import { WithId, Document, ObjectId } from 'mongodb';
|
||||
|
||||
let SERVER_COUNT = 0;
|
||||
|
||||
|
@ -8,7 +9,7 @@ const fetchStats = async () => {
|
|||
try {
|
||||
const res = await botReq('stats');
|
||||
if (!res.success) return logger.warn(`Failed to fetch bot stats: ${res.statusCode} / ${res.error}`);
|
||||
if (res.servers) SERVER_COUNT = Number(res.servers);
|
||||
if (res['servers']) SERVER_COUNT = Number(res['servers']);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -17,21 +18,37 @@ const fetchStats = async () => {
|
|||
fetchStats();
|
||||
setInterval(() => fetchStats(), 10000);
|
||||
|
||||
app.get('/stats', async (req: Request, res: Response) => {
|
||||
app.get('/stats', async (res: Response) => {
|
||||
res.send({
|
||||
servers: SERVER_COUNT,
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/stats/global_blacklist', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const users = await db.get('users').find({ globalBlacklist: true });
|
||||
|
||||
res.send({
|
||||
total: users.length,
|
||||
blacklist: users.map(u => ({ id: u.id?.toUpperCase(), reason: u.blacklistReason || null })),
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(''+e);
|
||||
}
|
||||
app.get('/stats/global_blacklist', async (res: Response) => {
|
||||
try {
|
||||
const dbConnection = await db;
|
||||
|
||||
const users = await dbConnection.collection('users').find({ globalBlacklist: true }).toArray();
|
||||
|
||||
res.send({
|
||||
total: users.length,
|
||||
blacklist: users.map((u: WithId<Document>) => ({
|
||||
id: getId(u._id),
|
||||
reason: (u as any).blacklistReason || null
|
||||
})),
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('Error fetching global blacklist:', e);
|
||||
res.status(500).send({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
function getId(id: string | ObjectId | undefined): string | null {
|
||||
if (typeof id === 'string') {
|
||||
return id.toUpperCase();
|
||||
} else if (id instanceof ObjectId) {
|
||||
return id.toHexString().toUpperCase();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,10 +66,10 @@ function requireAuth(config: RequireAuthConfig): (req: Request, res: Response, n
|
|||
|
||||
if (config.permission != undefined) {
|
||||
if (!auth) return unauthorized(res, 'Authentication required for this route');
|
||||
const server_id = req.params.serverid || req.params.server;
|
||||
const server_id = req.params['serverid'] || req.params['server'];
|
||||
const levelRes = await getPermissionLevel(auth, server_id);
|
||||
if (!levelRes.success) return res.status(500).send({ error: 'Unknown server or other error' });
|
||||
if (levelRes.level < config.permission) return unauthorized(res, 'Your permission level is too low');
|
||||
if (levelRes['level'] < config.permission) return unauthorized(res, 'Your permission level is too low');
|
||||
}
|
||||
|
||||
next();
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "node",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
|
Loading…
Reference in a new issue