De monk-ify
This commit is contained in:
parent
bfd2ea2094
commit
8d9bbc1893
7 changed files with 125 additions and 82 deletions
BIN
api/bun.lockb
BIN
api/bun.lockb
Binary file not shown.
|
@ -10,13 +10,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/monk": "^6.0.0",
|
||||
"@types/ws": "^8.5.11",
|
||||
"automod": "^0.1.0",
|
||||
"dotenv": "^14.3.2",
|
||||
"express": "^4.19.2",
|
||||
"log75": "^2.2.0",
|
||||
"monk": "^7.3.4",
|
||||
"mongodb": "^6.8.0",
|
||||
"redis": "^4.6.15",
|
||||
"ulid": "^2.3.0",
|
||||
"ws": "^8.18.0"
|
||||
|
|
|
@ -1,34 +1,44 @@
|
|||
import Monk, { IMonkManager } from 'monk';
|
||||
import { MongoClient, Db } from 'mongodb';
|
||||
import Redis from 'redis';
|
||||
import { logger } from '.';
|
||||
|
||||
export default (): IMonkManager => {
|
||||
let dburl = getDBUrl();
|
||||
let db = Monk(dburl);
|
||||
let db: Db;
|
||||
|
||||
export default async function buildDBClient(): Promise<Db> {
|
||||
if (db) return db;
|
||||
const url = getDBUrl();
|
||||
const client = new MongoClient(url);
|
||||
try {
|
||||
await client.connect();
|
||||
db = client.db();
|
||||
logger.info('Connected successfully to MongoDB');
|
||||
return db;
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Failed to connect to MongoDB', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const redis = Redis.createClient({ url: process.env.REDIS_URL });
|
||||
|
||||
export { redis }
|
||||
export { redis };
|
||||
|
||||
// Checks if all required env vars were supplied, and returns the mongo db URL
|
||||
function getDBUrl() {
|
||||
let env = process.env;
|
||||
function getDBUrl(): string {
|
||||
const env = process.env;
|
||||
if (env['DB_URL']) return env['DB_URL'];
|
||||
|
||||
if (!env['DB_HOST']) {
|
||||
logger.error(`Environment variable 'DB_HOST' not set, unable to connect to database`);
|
||||
logger.error(`Specify either 'DB_URL' or 'DB_HOST', 'DB_USERNAME', 'DB_PASS' and 'DB_NAME'`);
|
||||
throw 'Missing environment variables';
|
||||
throw new Error('Missing environment variables');
|
||||
}
|
||||
|
||||
// mongodb://username:password@hostname:port/dbname
|
||||
let dburl = 'mongodb://';
|
||||
if (env['DB_USERNAME']) dburl += env['DB_USERNAME'];
|
||||
if (env['DB_PASS']) dburl += `:${env['DB_PASS']}`;
|
||||
dburl += `${process.env['DB_USERNAME'] ? '@' : ''}${env['DB_HOST']}`; // DB_HOST is assumed to contain the port
|
||||
dburl += `${env['DB_USERNAME'] ? '@' : ''}${env['DB_HOST']}`; // DB_HOST is assumed to contain the port
|
||||
dburl += `/${env['DB_NAME'] ?? 'automod'}`;
|
||||
|
||||
return dburl;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,33 @@
|
|||
import { Request, Response } from "express";
|
||||
import { FindOneResult } from "monk";
|
||||
import { app, db, SESSION_LIFETIME } from "..";
|
||||
import { Collection, Db } from "mongodb";
|
||||
import { app, SESSION_LIFETIME } from "..";
|
||||
|
||||
let sessionsCollection: Collection;
|
||||
|
||||
export function initializeSessionsMiddleware(db: Db) {
|
||||
sessionsCollection = db.collection('sessions');
|
||||
}
|
||||
|
||||
app.use('*', async (req: Request, res: Response, next: () => void) => {
|
||||
next();
|
||||
|
||||
const user = req.header('x-auth-user');
|
||||
const token = req.header('x-auth-token');
|
||||
|
||||
if (!user || !token) return;
|
||||
|
||||
try {
|
||||
const session: FindOneResult<any> = await db.get('sessions').findOne({ user, token, expires: { $gt: Date.now() } });
|
||||
const session = await sessionsCollection.findOne({
|
||||
user,
|
||||
token,
|
||||
expires: { $gt: new Date() }
|
||||
});
|
||||
|
||||
if (session) {
|
||||
await db.get('sessions').update({ _id: session._id }, { $set: { expires: Date.now() + SESSION_LIFETIME } });
|
||||
await sessionsCollection.updateOne(
|
||||
{ _id: session._id },
|
||||
{ $set: { expires: new Date(Date.now() + SESSION_LIFETIME) } }
|
||||
);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
} catch(e) { console.error(e) }
|
||||
});
|
||||
|
|
|
@ -2,9 +2,15 @@ import { app, db } from '../..';
|
|||
import { Request, Response } from 'express';
|
||||
import { badRequest, ensureObjectStructure, isAuthenticated, requireAuth, unauthorized } from '../../utils';
|
||||
import { botReq } from '../internal/ws';
|
||||
import { FindOneResult } from 'monk';
|
||||
import { Collection, Db, ObjectId } from 'mongodb';
|
||||
import { ulid } from 'ulid';
|
||||
|
||||
let serversCollection: Collection;
|
||||
|
||||
export function initializeAutomodAPI(database: Db) {
|
||||
serversCollection = database.collection('servers');
|
||||
}
|
||||
|
||||
type AntispamRule = {
|
||||
id: string;
|
||||
max_msg: number;
|
||||
|
@ -14,7 +20,7 @@ type AntispamRule = {
|
|||
message: string | null;
|
||||
}
|
||||
|
||||
app.get('/dash/server/:server/automod',requireAuth({ permission: 2 }) , async (req: Request, res: Response) => {
|
||||
app.get('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (req: Request, res: Response) => {
|
||||
const user = await isAuthenticated(req, res, true);
|
||||
if (!user) return;
|
||||
|
||||
|
@ -31,11 +37,11 @@ app.get('/dash/server/:server/automod',requireAuth({ permission: 2 }) , async (r
|
|||
const permissionLevel: 0|1|2|3 = response.perms;
|
||||
if (permissionLevel < 1) return unauthorized(res, `Only moderators and bot managers may view this.`);
|
||||
|
||||
const serverConfig: FindOneResult<any> = await db.get('servers').findOne({ id: server });
|
||||
const serverConfig = await serversCollection.findOne({ id: server });
|
||||
|
||||
const result = {
|
||||
antispam: (serverConfig?.automodSettings?.spam as AntispamRule[]|undefined)
|
||||
?.map(r => ({ // Removing unwanted fields from response
|
||||
?.map(r => ({
|
||||
action: r.action,
|
||||
channels: r.channels,
|
||||
id: r.id,
|
||||
|
@ -57,28 +63,29 @@ app.patch('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 })
|
|||
const body = req.body;
|
||||
if (!server || !ruleid) return badRequest(res);
|
||||
|
||||
const serverConfig: FindOneResult<any> = await db.get('servers').findOne({ id: server });
|
||||
const antiSpamRules: AntispamRule[] = serverConfig.automodSettings?.spam ?? [];
|
||||
const serverConfig = await serversCollection.findOne({ id: server });
|
||||
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.' });
|
||||
|
||||
await db.get('servers').update({
|
||||
id: server
|
||||
}, {
|
||||
const result = await serversCollection.updateOne(
|
||||
{ id: server, "automodSettings.spam.id": ruleid },
|
||||
{
|
||||
$set: {
|
||||
"automodSettings.spam.$[rulefilter]": {
|
||||
"automodSettings.spam.$": {
|
||||
...rule,
|
||||
action: Number(body.action ?? rule.action),
|
||||
channels: body.channels ?? rule.channels,
|
||||
message: body.message ?? rule.message,
|
||||
max_msg: body.max_msg ?? rule.max_msg,
|
||||
timeframe: body.timeframe ?? rule.timeframe,
|
||||
} as AntispamRule
|
||||
}
|
||||
}, { arrayFilters: [ { "rulefilter.id": ruleid } ] });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return res.send({ success: true });
|
||||
return res.send({ success: result.modifiedCount > 0 });
|
||||
});
|
||||
|
||||
app.post('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (req, res) => {
|
||||
|
@ -109,9 +116,9 @@ app.post('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (
|
|||
|
||||
const id = ulid();
|
||||
|
||||
await db.get('servers').update({
|
||||
id: server,
|
||||
}, {
|
||||
const result = await serversCollection.updateOne(
|
||||
{ id: server },
|
||||
{
|
||||
$push: {
|
||||
"automodSettings.spam": {
|
||||
id: id,
|
||||
|
@ -121,9 +128,10 @@ app.post('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (
|
|||
message: rule.message ?? null,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200).send({ success: true, id: id });
|
||||
res.status(200).send({ success: result.modifiedCount > 0, id: id });
|
||||
});
|
||||
|
||||
app.delete('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 }), async (req, res) => {
|
||||
|
@ -140,22 +148,22 @@ app.delete('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 }
|
|||
|
||||
if (!response.server) return res.status(404).send({ error: 'Server not found' });
|
||||
|
||||
// todo: fix this shit idk if it works
|
||||
let queryRes;
|
||||
let result;
|
||||
try {
|
||||
queryRes = await db.get('servers').update({
|
||||
id: server
|
||||
}, {
|
||||
result = await serversCollection.updateOne(
|
||||
{ id: server },
|
||||
{
|
||||
$pull: {
|
||||
"automodSettings.spam": { id: ruleid }
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
res.status(500).send({ error: e });
|
||||
return;
|
||||
}
|
||||
|
||||
if (queryRes.nModified > 0) res.status(200).send({ success: true });
|
||||
if (result.modifiedCount > 0) res.status(200).send({ success: true });
|
||||
else res.status(404).send({ success: false, error: 'Rule not found' });
|
||||
});
|
||||
|
|
|
@ -2,14 +2,22 @@ import crypto from 'crypto';
|
|||
import { app, SESSION_LIFETIME } from '..';
|
||||
import { Request, Response } from 'express';
|
||||
import { botReq } from './internal/ws';
|
||||
import { db } from '..';
|
||||
import { FindOneResult } from 'monk';
|
||||
import { Collection, Db } from 'mongodb';
|
||||
import { badRequest, isAuthenticated, requireAuth } from '../utils';
|
||||
import { RateLimiter } from '../middlewares/ratelimit';
|
||||
|
||||
let pendingLoginsCollection: Collection;
|
||||
let sessionsCollection: Collection;
|
||||
|
||||
export function initializeAuthAPI(database: Db) {
|
||||
pendingLoginsCollection = database.collection('pending_logins');
|
||||
sessionsCollection = database.collection('sessions');
|
||||
}
|
||||
|
||||
class BeginReqBody {
|
||||
user: string;
|
||||
}
|
||||
|
||||
class CompleteReqBody {
|
||||
user: string;
|
||||
nonce: string;
|
||||
|
@ -24,14 +32,10 @@ app.post('/login/begin',
|
|||
requireAuth({ noAuthOnly: true }),
|
||||
async (req: Request, res: Response) => {
|
||||
if (typeof await isAuthenticated(req) == 'string') return res.status(403).send({ error: 'You are already authenticated' });
|
||||
|
||||
const body = req.body as BeginReqBody;
|
||||
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 });
|
||||
});
|
||||
|
||||
|
@ -44,7 +48,7 @@ app.post('/login/complete',
|
|||
(!body.nonce || typeof body.nonce != 'string') ||
|
||||
(!body.code || typeof body.code != 'string')) return badRequest(res);
|
||||
|
||||
const loginAttempt: FindOneResult<any> = await db.get('pending_logins').findOne({
|
||||
const loginAttempt = await pendingLoginsCollection.findOne({
|
||||
code: body.code,
|
||||
user: body.user,
|
||||
nonce: body.nonce,
|
||||
|
@ -53,23 +57,24 @@ app.post('/login/complete',
|
|||
});
|
||||
|
||||
if (!loginAttempt) return res.status(404).send({ error: 'The provided login info could not be found.' });
|
||||
|
||||
if (!loginAttempt.confirmed) {
|
||||
return res.status(400).send({ error: "This code is not yet valid." });
|
||||
}
|
||||
|
||||
const sessionToken = crypto.randomBytes(48).toString('base64').replace(/=/g, '');
|
||||
|
||||
|
||||
await Promise.all([
|
||||
db.get('sessions').insert({
|
||||
sessionsCollection.insertOne({
|
||||
user: body.user.toUpperCase(),
|
||||
token: sessionToken,
|
||||
nonce: body.nonce,
|
||||
invalid: false,
|
||||
expires: Date.now() + SESSION_LIFETIME,
|
||||
}),
|
||||
db.get('pending_logins').update({ _id: loginAttempt._id }, { $set: { exchanged: true } }),
|
||||
pendingLoginsCollection.updateOne(
|
||||
{ _id: loginAttempt._id },
|
||||
{ $set: { exchanged: true } }
|
||||
),
|
||||
]);
|
||||
|
||||
res.status(200).send({ success: true, user: body.user.toUpperCase(), token: sessionToken });
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import { Request, Response } from "express";
|
||||
import { FindOneResult } from "monk";
|
||||
import { db } from ".";
|
||||
import { Collection, Db } from "mongodb";
|
||||
import { botReq } from "./routes/internal/ws";
|
||||
|
||||
let sessionsCollection: Collection;
|
||||
|
||||
export function initializeSessionAuthentication(db: Db) {
|
||||
sessionsCollection = db.collection('sessions');
|
||||
}
|
||||
|
||||
class Session {
|
||||
user: string;
|
||||
token: string;
|
||||
|
@ -19,9 +24,7 @@ class Session {
|
|||
async function isAuthenticated(req: Request, res?: Response, send401?: boolean): Promise<string|false> {
|
||||
const user = req.header('x-auth-user');
|
||||
const token = req.header('x-auth-token');
|
||||
|
||||
if (!user || !token) return false;
|
||||
|
||||
const info = await getSessionInfo(user, token);
|
||||
if (res && send401 && !info.valid) {
|
||||
res.status(401).send({ error: 'Unauthorized' });
|
||||
|
@ -32,9 +35,13 @@ async function isAuthenticated(req: Request, res?: Response, send401?: boolean):
|
|||
type SessionInfo = { exists: boolean, valid: boolean, nonce?: string }
|
||||
|
||||
async function getSessionInfo(user: string, token: string): Promise<SessionInfo> {
|
||||
const session: FindOneResult<Session> = await db.get('sessions').findOne({ user, token });
|
||||
const session = await sessionsCollection.findOne<Session>({ user, token });
|
||||
|
||||
return { exists: !!session, valid: !!(session && !session.invalid && session.expires > Date.now()), nonce: session?.nonce }
|
||||
return {
|
||||
exists: !!session,
|
||||
valid: !!(session && !session.invalid && session.expires > Date.now()),
|
||||
nonce: session?.nonce
|
||||
}
|
||||
}
|
||||
|
||||
function badRequest(res: Response, infoText?: string) {
|
||||
|
|
Loading…
Reference in a new issue