Skip to content

Commit

Permalink
Added types for the new auth system
Browse files Browse the repository at this point in the history
  • Loading branch information
AshMW2724 committed Dec 28, 2023
1 parent f683c70 commit 37867f7
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 41 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"cSpell.words": ["precors"]
}
59 changes: 39 additions & 20 deletions src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,40 @@ import { Server as HttpServer, IncomingMessage as HttpIncomingMessage, ServerRes
import indexFolder from './utils/indexFolder';
import { OpenAPIV3_1 as OpenAPI } from 'openapi-types';
import Documentation, { APIInfoObject } from './documentation';
import { HTTPContext, RouteFile, ExpressErrorResponse } from './types/httprouter';
import { HTTPContext, RouteFile, ExpressErrorResponse, RouteConfig, RouteAuthenticationMethodWithData } from './types/httprouter';
import { HttpStatus } from 'utils/httpStatus';
import { MiddlewareWhen, RouteMiddleware, ServerConfig } from './types/server';
import { AuthenticationMethods, MiddlewareWhen, RouteMiddleware, ServerConfig } from './types/server';

interface OASchemaFile {
enabled: boolean;
publicSchemas: boolean;
schemas: Record<string, OpenAPI.SchemaObject>;
}

export class Server<Context = {}> {
config: ServerConfig<Context>;
export class Server<Context extends {}, Methods extends AuthenticationMethods<Context>> {
config: ServerConfig<Context, Methods>;
express: express.Express;
server: HttpServer<typeof HttpIncomingMessage, typeof HttpServerResponse> | undefined;
startedAt: Date | null;
wss: WebSocketServer | undefined;
documentation: Documentation | undefined;
middleware: RouteMiddleware[];

constructor(config: ServerConfig<Context>) {
// types: Used to generate type, ignore safely
routeConfig: RouteConfig<Context, Methods>;
routeHandler<MoreContext = {}>(ctx: Context & MoreContext): any {}
// /types

constructor(config: ServerConfig<Context, Methods>) {
this.config = config;

// types: Used to generate type, ignore safely
this.routeConfig = {} as any;
// /types

this.wss = config.websocket.enabled ? config.websocket.wss || new WebSocketServer({ noServer: true }) : undefined;
this.documentation = config.routes.enabled && config.routes.documentation.enabled ? new Documentation(config.routes.documentation.open_api) : undefined;
this.documentation =
config.routes.enabled && config.routes.documentation.enabled ? new Documentation(config.routes.documentation.open_api) : undefined;
this.middleware = config?.routes?.middleware || [];

this.startedAt = null;
Expand Down Expand Up @@ -78,12 +88,12 @@ export class Server<Context = {}> {
const runMiddleware = async (when: MiddlewareWhen) => {
const middleware = this.middleware.filter((x) => x.when == when).sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
middleware.forEach((x) => this.express.use(x.handle));
}
runMiddleware('init')
};
runMiddleware('init');
this.express.use(express.json({ limit: '50mb' }));
this.express.use(express.urlencoded({ extended: true, limit: '50mb' }));
if (this.config.cors.enabled) {
runMiddleware('precors')
runMiddleware('precors');
this.express.use((req, res, next) => {
//@ts-expect-error Cors is enabled... stupid typescript
if (this.config.cors.origin != '*') res.header('Access-Control-Allow-Origin', this.config.cors.origin);
Expand Down Expand Up @@ -141,8 +151,8 @@ export class Server<Context = {}> {
if (this.config.routes?.security?.authentication) {
const auth = this.config.routes.security.authentication;
if (!auth.enabled) return log('error', 'Authentication is enabled but no authentication function is defined');
if (!auth.handle) return log('error', 'Authentication is enabled but no authentication function is defined');
if (typeof auth.handle != 'function') return log('error', 'Authentication handle must be a function');
// if (!auth.handle) return log('error', 'Authentication is enabled but no authentication function is defined');
// if (typeof auth.handle != 'function') return log('error', 'Authentication handle must be a function');

authFunc = (req: express.Request, res: express.Response, next: express.NextFunction) => {
const errorResponse = (status: HttpStatus, opts?: { message?: string; data?: any; code?: string }) => {
Expand All @@ -151,7 +161,7 @@ export class Server<Context = {}> {
.send({ error: true, status: status, code: opts?.code || HttpStatus[status], message: opts?.message, data: opts?.data });
};
try {
return auth.handle({ ...this.config.routes.context, req, res, next, errorResponse });
// return auth.handle({ ...this.config.routes.context, req, res, next, errorResponse });
} catch (err) {
log('error', `Middleware Error: ${(err as Error).message}`);
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, { message: (err as Error).message, code: 'ERROR_UNKNOWN_MIDDLEWARE' });
Expand Down Expand Up @@ -199,23 +209,22 @@ export class Server<Context = {}> {
return route.handler({ ...this.config.routes.context, req, res, next, errorResponse });
} catch (error) {
log('error', (error as Error).message ?? 'Unknown error');
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, { message: (error as Error).message ?? "Unknown error", code: "ERROR_UNKNOWN_ROUTE"})
return errorResponse(HttpStatus.INTERNAL_SERVER_ERROR, {
message: (error as Error).message ?? 'Unknown error',
code: 'ERROR_UNKNOWN_ROUTE',
});
}
};

if (route.configuration.security?.authentication && authFunc != null) this.express[method](routePath.replace(/\[([^\]]+)\]/g, ':$1'), authFunc, routeFunc);
if (route.configuration.security?.authentication && authFunc != null)
this.express[method](routePath.replace(/\[([^\]]+)\]/g, ':$1'), authFunc, routeFunc);
else this.express[method](routePath.replace(/\[([^\]]+)\]/g, ':$1'), routeFunc);
}
runMiddleware('postroutes');
}
runMiddleware('finish');
}

/**
* @description Allows you for type-safe shit ig
*/
routeHandler<MoreContext = {}>(ctx: Context & MoreContext): any {}

async listen() {
if (this.validateConfig() != true) return;
if (this.startedAt !== null) return log('error', 'Server already listening');
Expand Down Expand Up @@ -253,5 +262,15 @@ export class Server<Context = {}> {
this.server?.close();
this.wss?.close();
}
}

/**
* Helper Functions
*/

authenticationMethod<Method extends keyof Methods>(
method: Method,
data: Parameters<Methods[Method]>[1],
): RouteAuthenticationMethodWithData<Context, Methods, Method> {
return { method, data } as RouteAuthenticationMethodWithData<Context, Methods, Method>;
}
}
17 changes: 14 additions & 3 deletions src/types/httprouter.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import express from 'express';
import { Documentation } from './documentation';
import { HttpStatus } from '../utils/httpStatus';
import { AuthenticationMethods } from './server';

export interface RouteFile<Handler> {
configuration?: RouteConfig;
configuration?: RouteConfig<any, any>;
handler: Handler;
}

export interface RouteConfig {
export interface RouteAuthenticationMethodWithData<
Context extends {},
Methods extends AuthenticationMethods<Context>,
Method extends keyof Methods = keyof Methods,
> {
method: Method;
// This should be the type of the specified method above, but it is a union of all the possible types.
data: any;
}

export interface RouteConfig<Context extends {}, Methods extends AuthenticationMethods<Context>> {
enabled: boolean;
security?: {
authentication?: boolean;
authentication?: (RouteAuthenticationMethodWithData<Context, Methods> | keyof Methods)[];
};
documentation: Documentation;
}
Expand Down
35 changes: 24 additions & 11 deletions src/types/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ import { ExpressErrorResponse, HTTPContext } from './httprouter';
import { APIInfoObject } from 'documentation';
import { WebSocketServer } from 'ws';

export type CtxMiddlewareFunction<Context> = (ctx: Context & HTTPContext) => (express.NextFunction | ExpressErrorResponse | void) | Promise<express.NextFunction | ExpressErrorResponse | void>;
export type MiddlewareFunction = (req: express.Request, res: express.Response, next: express.NextFunction) => (express.NextFunction | void) | Promise<express.NextFunction | void>;
export type CtxMiddlewareFunction<Context = {}> = (
ctx: Context & HTTPContext,
data?: any,
) => (express.NextFunction | ExpressErrorResponse | void) | Promise<express.NextFunction | ExpressErrorResponse | void>;
export type MiddlewareFunction = (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => (express.NextFunction | void) | Promise<express.NextFunction | void>;
export type MiddlewareWhen = 'init' | 'precors' | 'postcors' | 'predocs' | 'postdocs' | 'preroutes' | 'postroutes' | 'finish';
export interface RouteMiddleware {
name: string;
Expand All @@ -13,7 +20,18 @@ export interface RouteMiddleware {
handle: MiddlewareFunction;
}

export interface ServerConfig<Context = {}> {
export interface AuthenticationMethods<Context = {}> {
[key: string]: CtxMiddlewareFunction<Context>;
}

export type Authentication<Context, Methods extends AuthenticationMethods<Context>> =
| { enabled: false }
| {
enabled: true;
methods: Methods;
};

export interface ServerConfig<Context extends {}, Methods extends AuthenticationMethods<Context>> {
port: number;
host: string;
cors: { enabled: false } | { enabled: true; origin: string };
Expand All @@ -23,13 +41,8 @@ export interface ServerConfig<Context = {}> {
context: Context;
middleware: RouteMiddleware[];
security?: {
authentication?:
| { enabled: false }
| {
enabled: true;
handle: CtxMiddlewareFunction<Context>;
};
}
authentication?: Authentication<Context, Methods>;
};
documentation:
| { enabled: false }
| {
Expand All @@ -46,4 +59,4 @@ export interface ServerConfig<Context = {}> {
path: string;
wss?: WebSocketServer;
};
}
}
22 changes: 18 additions & 4 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const context = {
test: 'test',
};

const api = new Server<typeof context>({
const api = new Server({
port: 3000,
host: '0.0.0.0',
cors: {
Expand All @@ -16,6 +16,17 @@ const api = new Server<typeof context>({
enabled: true,
folder: './routes',
context: context,
security: {
authentication: {
enabled: true,
methods: {
loggedIn: () => undefined,
hasPermission: (ctx, { penis }: { penis: boolean }) => undefined,
needsBot: () => undefined,
cumInAssable: () => undefined,
},
},
},
middleware: [
{
name: 'test',
Expand All @@ -24,7 +35,7 @@ const api = new Server<typeof context>({
console.log('this is before anything ever happens!');
next();
},
}
},
],
documentation: {
enabled: true,
Expand All @@ -35,7 +46,7 @@ const api = new Server<typeof context>({
license: {
name: 'MIT',
identifier: 'MIT',
}
},
},
externalDocs: {
url: 'https://example.com',
Expand All @@ -44,7 +55,7 @@ const api = new Server<typeof context>({
servers: [
{
url: 'https://example.com/',
}
},
],
},
path: '/docs',
Expand All @@ -56,7 +67,10 @@ const api = new Server<typeof context>({
},
});

// Export necessary shit
export type RouteHandler = typeof api.routeHandler<HTTPContext>;
export type RouteConfig = typeof api.routeConfig;
export const authenticationMethod = api.authenticationMethod;

// Prior to starting the server, you can middleware
// api.addMiddleware({
Expand Down
13 changes: 11 additions & 2 deletions test/routes/get.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { RouteConfig } from '../../src';
import type { RouteHandler } from '../index';
import { RouteConfig, RouteHandler, authenticationMethod } from '../index';

export const handler: RouteHandler = (ctx) => ctx.res.json({ message: 'Hey! Go to the docs... not the api root...' });

Expand All @@ -9,4 +8,14 @@ export const configuration: RouteConfig = {
public: false,
operationId: 'api_root',
},
security: {
authentication: [
'loggedIn',
authenticationMethod('hasPermission', { penis: false }),
{
method: 'cumInAssable',
data: 'anything',
},
],
},
};

0 comments on commit 37867f7

Please sign in to comment.