This commit is contained in:
ADAMJR 2021-11-02 18:51:52 +00:00
parent 43f721e0b7
commit 7e4cc4b1ad
13 changed files with 144 additions and 136 deletions

View File

@ -1,10 +1,14 @@
import Users from '../data/users';
import { REST } from '../rest/server';
interface Deps {
export interface Deps {
rest: REST;
users: Users;
}
export const deps: Deps = {
const deps: Deps = {
rest: new REST(),
users: new Users(),
};
export default deps;
global['deps'] = deps;

View File

@ -0,0 +1,21 @@
import { NextFunction, Request, Response } from 'express';
import { Application } from 'express-serve-static-core';
import { APIError } from '../modules/api-error';
export default (app: Application, prefix: string) => {
app.all(`${prefix}/*`, (req, res, next) => next(new APIError(404)));
app.use(`/`, () => {
throw new TypeError('Invalid API version number');
});
app.use((error: APIError, req: Request, res: Response, next: NextFunction) => {
if (res.headersSent)
return next(error);
const code = error.code || 400;
return res
.status(code)
.json({ message: error.message });
});
}

View File

@ -0,0 +1,21 @@
import { Application } from 'express-serve-static-core';
import bodyParser from 'body-parser';
import { Strategy as LocalStrategy } from 'passport-local';
import passport from 'passport';
import cors from 'cors';
import { User } from '../../data/models/user';
import rateLimiter from '../modules/rate-limiter';
export default (app: Application) => {
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(User as any).authenticate(),
));
passport.serializeUser((User as any).serializeUser());
passport.deserializeUser((User as any).deserializeUser());
app.use(cors());
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(rateLimiter);
}

View File

@ -0,0 +1,21 @@
import { Application } from 'express-serve-static-core';
import { router as apiRoutes } from '../routes/api-routes';
import { router as authRoutes } from '../routes/auth-routes';
import { router as channelsRoutes } from '../routes/channel-routes';
import { router as guildsRoutes } from '../routes/guilds-routes';
import { router as usersRoutes } from '../routes/users-routes';
import { router as invitesRoutes } from '../routes/invites-routes';
import { resolve } from 'path';
import express from 'express';
export default (app: Application, prefix: string) => {
app.use(`/assets`, express.static(resolve('./assets')));
app.use(`${prefix}`, apiRoutes);
app.use(`${prefix}/auth`, authRoutes);
app.use(`${prefix}/invites`, invitesRoutes);
// app.use(`${prefix}/dev`, devRoutes);
app.use(`${prefix}/channels`, channelsRoutes);
app.use(`${prefix}/guilds`, guildsRoutes);
app.use(`${prefix}/users`, usersRoutes);
}

View File

@ -0,0 +1,6 @@
import { NextFunction, Request, Response } from 'express';
export default async (req: Request, res: Response, next: NextFunction) => {
res.locals.guild = await deps.guilds.get(req.params.id);
return next();
}

View File

@ -0,0 +1,12 @@
import { NextFunction, Request, Response } from 'express';
export default async (req: Request, res: Response, next: NextFunction) => {
try {
const token = req.get('Authorization') as string;
const id = await deps.users.idFromToken(token);
res.locals.user = await deps.users.getSelf(id);
} finally {
return next();
}
}

View File

@ -0,0 +1,9 @@
import { NextFunction, Request, Response } from 'express';
import { Guild } from '../../data/models/guild';
export default async (req: Request, res: Response, next: NextFunction) => {
const exists = await Guild.exists({ _id: req.params.id });
return (exists)
? next()
: res.status(404).json({ message: 'Guild does not exist' });
}

View File

@ -0,0 +1,9 @@
import { NextFunction, Request, Response } from 'express';
import { APIError } from '../modules/api-error';
export default async (req: Request, res: Response, next: NextFunction) => {
const userOwnsGuild = res.locals.guild.ownerId === res.locals.user.id;
if (userOwnsGuild)
return next();
throw new APIError(401, 'You do not own this guild!');
}

View File

@ -0,0 +1,19 @@
import { NextFunction, Request, Response } from 'express';
import { GuildDocument } from '../../data/models/guild';
import { PermissionTypes } from '../../types/permission-types';
import { APIError } from '../modules/api-error';
export default (permission: PermissionTypes.Permission) =>
async (req: Request, res: Response, next: NextFunction) => {
const guild: GuildDocument = res.locals.guild;
const members = await deps.guilds.getMembers(guild.id);
const member = members.find(m => m.userId === res.locals.user.id);
if (!member)
throw new APIError(401, 'You are not a guild member');
const isOwner = guild.ownerId === res.locals.user.id;
const hasPerm = await deps.roles.hasPermission(guild, member, permission);
if (hasPerm || isOwner) return next();
throw new APIError(401, `Missing Permissions: ${permission}`);
};

View File

@ -0,0 +1,8 @@
import { NextFunction, Request, Response } from 'express';
import { APIError } from '../modules/api-error';
export default async (req: Request, res: Response, next: NextFunction) => {
if (res.locals.user)
return next();
throw new APIError(401, 'User not logged in');
}

View File

@ -1,64 +0,0 @@
import Guilds from '../../data/guilds';
import { Guild, GuildDocument } from '../../data/models/guild';
import Roles from '../../data/roles';
import Users from '../../data/users';
import Deps from '../../utils/deps';
import { APIError } from './api-error';
import { NextFunction, Request, Response } from 'express';
import { PermissionTypes } from '../../types/permission-types';
const guilds = Deps.get<Guilds>(Guilds);
const roles = Deps.get<Roles>(Roles);
const users = Deps.get<Users>(Users);
export async function updateUser(req: Request, res: Response, next: NextFunction) {
try {
const token = req.get('Authorization') as string;
const id = await users.idFromToken(token);
res.locals.user = await users.getSelf(id);
} finally {
return next();
}
}
export function validateUser(req: Request, res: Response, next: NextFunction) {
if (res.locals.user)
return next();
throw new APIError(401, 'User not logged in');
}
export async function updateGuild(req: Request, res: Response, next: NextFunction) {
res.locals.guild = await guilds.get(req.params.id);
return next();
}
export async function validateGuildExists(req: Request, res: Response, next: NextFunction) {
const exists = await Guild.exists({ _id: req.params.id });
return (exists)
? next()
: res.status(404).json({ message: 'Guild does not exist' });
}
export async function validateGuildOwner(req: Request, res: Response, next: NextFunction) {
const userOwnsGuild = res.locals.guild.ownerId === res.locals.user.id;
if (userOwnsGuild)
return next();
throw new APIError(401, 'You do not own this guild!');
}
export function validateHasPermission(permission: PermissionTypes.Permission) {
return async (req: Request, res: Response, next: NextFunction) => {
const guild: GuildDocument = res.locals.guild;
const members = await guilds.getMembers(guild.id);
const member = members.find(m => m.userId === res.locals.user.id);
if (!member)
throw new APIError(401, 'You are not a guild member');
const isOwner = guild.ownerId === res.locals.user.id;
const hasPerm = await roles.hasPermission(guild, member, permission);
if (hasPerm || isOwner) return next();
throw new APIError(401, `Missing Permissions: ${permission}`);
};
}

View File

@ -1,81 +1,22 @@
import express, { NextFunction, Request, Response } from 'express';
import express from 'express';
import 'express-async-errors';
import bodyParser from 'body-parser';
import { Strategy as LocalStrategy } from 'passport-local';
import passport from 'passport';
import { router as apiRoutes } from './routes/api-routes';
import { router as authRoutes } from './routes/auth-routes';
import { router as channelsRoutes } from './routes/channel-routes';
import { router as guildsRoutes } from './routes/guilds-routes';
import { router as usersRoutes } from './routes/users-routes';
import { router as invitesRoutes } from './routes/invites-routes';
import { User } from '../data/models/user';
import cors from 'cors';
import { resolve } from 'path';
import Deps from '../utils/deps';
import { WebSocket } from '../ws/websocket';
import { APIError } from './modules/api-error';
import rateLimiter from './modules/rate-limiter';
import applyMiddleware from './functions/apply-middleware';
import applyRoutes from './functions/apply-routes';
import applyErrorHandling from './functions/apply-error-handling';
export class REST {
public app = express();
private prefix = `/v2`;
constructor(private ws = Deps.get<WebSocket>(WebSocket)) {
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
this.listen();
}
private setupMiddleware() {
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(User as any).authenticate(),
));
passport.serializeUser((User as any).serializeUser());
passport.deserializeUser((User as any).deserializeUser());
this.app.use(cors());
this.app.use(bodyParser.json());
this.app.use(passport.initialize());
this.app.use(rateLimiter);
}
private setupRoutes() {
this.app.use(`/assets`, express.static(resolve('./assets')));
this.app.use(`${this.prefix}`, apiRoutes);
const app = express();
const prefix = `/v2`;
this.app.use(`${this.prefix}/auth`, authRoutes);
this.app.use(`${this.prefix}/invites`, invitesRoutes);
// this.app.use(`${this.prefix}/dev`, devRoutes);
this.app.use(`${this.prefix}/channels`, channelsRoutes);
this.app.use(`${this.prefix}/guilds`, guildsRoutes);
this.app.use(`${this.prefix}/users`, usersRoutes);
}
applyMiddleware(app);
applyRoutes(app, prefix);
applyErrorHandling(app, prefix);
private setupErrorHandling() {
this.app.all(`${this.prefix}/*`, (req, res, next) => next(new APIError(404)));
this.app.use(`/`, () => {
throw new TypeError('Invalid API version number');
});
this.app.use((error: APIError, req: Request, res: Response, next: NextFunction) => {
if (res.headersSent)
return next(error);
const code = error.code || 400;
return res
.status(code)
.json({ message: error.message });
});
}
private listen() {
const port = process.env.PORT || 8080;
const server = this.app.listen(port, async () => {
const server = app.listen(port, async () => {
log.info(`API is running on port ${port}`);
await this.ws.init(server);
});

1
types/global.d.ts vendored
View File

@ -1,3 +1,4 @@
export declare global {
const log: import('winston').Logger;
const deps: import('../modules/deps').Deps;
}