middleware-express
Version:
middleware-express
371 lines (369 loc) • 14.3 kB
text/typescript
import { NextFunction, Request, Response } from 'express';
export interface StatusError {
status: number;
body: string;
}
export class Handler<P> {
buildError: (err: any) => StatusError;
prefix: string;
token: string;
constructor(public secret: string, public verify: (token: string, secret: string) => Promise<P>, buildErr?: (err: any) => StatusError, prefix?: string, token?: string) {
this.buildError = (buildErr ? buildErr : buildError);
this.prefix = (prefix ? prefix : 'Bearer ');
this.token = (token ? token : 'token');
this.check = this.check.bind(this);
this.require = this.require.bind(this);
}
check(): (req: Request, res: Response, next: NextFunction) => void {
return (req: Request, res: Response, next: NextFunction) => {
const data = req.headers['authorization'];
if (data) {
if (!data.startsWith(this.prefix)) {
res.status(401).end(`Authorization must start with '${this.prefix.trim()}'`);
} else {
const token = data.substr(this.prefix.length);
this.verify(token, this.secret).then(payload => {
res.locals[this.token] = payload;
next();
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
} else {
next();
}
};
}
require(): (req: Request, res: Response, next: NextFunction) => void {
return this.check();
}
}
export const AuthorizationHandler = Handler;
export class AuthorizationChecker<P> {
buildError: (err: any) => StatusError;
prefix: string;
token: string;
constructor(public secret: string, public verify: (token: string, secret: string) => Promise<P>, buildErr?: (err: any) => StatusError, prefix?: string, token?: string) {
this.buildError = (buildErr ? buildErr : buildError);
this.prefix = (prefix ? prefix : 'Bearer ');
this.token = (token ? token : 'token');
this.check = this.check.bind(this);
this.require = this.require.bind(this);
}
check(): (req: Request, res: Response, next: NextFunction) => void {
return (req: Request, res: Response, next: NextFunction) => {
const data = req.headers['authorization'];
if (data) {
if (!data.startsWith(this.prefix)) {
res.status(401).end(`Authorization must start with '${this.prefix.trim()}'`);
} else {
const token = data.substr(this.prefix.length);
this.verify(token, this.secret).then(payload => {
res.locals[this.token] = payload;
next();
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
} else {
res.status(401).end(`Require 'Authorization' in header`);
}
};
}
require(): (req: Request, res: Response, next: NextFunction) => void {
return this.check();
}
}
export function exist<T>(obj: T | T[], arr: T[]): boolean {
if (Array.isArray(obj)) {
for (const o of obj) {
for (const v of arr) {
// tslint:disable-next-line:triple-equals
if (o == v) {
return true;
}
}
}
} else {
for (const v of arr) {
// tslint:disable-next-line:triple-equals
if (obj == v) {
return true;
}
}
}
return false;
}
export class QuickChecker<T> {
buildError: (err: any) => StatusError;
key: string;
token: string;
constructor(key?: string, buildErr?: (err: any) => StatusError, token?: string) {
this.buildError = (buildErr ? buildErr : buildError);
this.key = (key ? key : 'userId');
this.token = (token ? token : 'token');
this.check = this.check.bind(this);
}
check(v: T[]): (req: Request, res: Response, next: NextFunction) => void {
return (req: Request, res: Response, next: NextFunction) => {
const payload: any = res.locals[this.token];
if (!payload) {
res.status(401).end('Payload cannot be undefined');
} else {
const obj = (payload as any)[this.key];
if (!obj) {
res.status(403).end('Payload must contain ' + this.key);
} else {
if (exist<T>(obj as any, v)) {
next();
} else {
res.status(403).end('invalid ' + this.key);
}
}
}
};
}
}
export class Checker<T, P> {
buildError: (err: any) => StatusError;
key: string;
prefix: string;
token: string;
constructor(public secret: string, public verify: (token: string, secret: string) => Promise<P>, key?: string, buildErr?: (err: any) => StatusError, prefix?: string, token?: string) {
this.buildError = (buildErr ? buildErr : buildError);
this.prefix = (prefix ? prefix : 'Bearer ');
this.key = (key ? key : 'userId');
this.token = (token ? token : 'token');
this.check = this.check.bind(this);
}
check(v: T[]): (req: Request, res: Response, next: NextFunction) => void {
return (req: Request, res: Response, next: NextFunction) => {
const data = req.headers['authorization'];
if (data) {
if (!data.startsWith(this.prefix)) {
res.status(401).end(`Authorization must start with '${this.prefix.trim()}'`);
} else {
const token = data.substr(this.prefix.length);
this.verify(token, this.secret).then(payload => {
if (payload === undefined) {
res.status(401).end('Payload cannot be undefined');
} else {
res.locals[this.token] = payload;
const obj = (payload as any)[this.key];
if (!obj) {
res.status(403).end('Payload must contain ' + this.key);
} else {
if (exist<T>(obj as any, v)) {
next();
} else {
res.status(403).end('invalid ' + this.key);
}
}
}
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
} else {
res.status(401).end(`Require 'Authorization' in header`);
}
};
}
}
export class MultiAuthorizer<T, P> {
buildError: (err: any) => StatusError;
user: string;
key: string;
prefix: string;
exact: boolean;
token: string;
constructor(public secret: string, public verify: (token: string, secret: string) => Promise<P>, public privilege: (userId: string, privilegeId: string) => Promise<number>, key?: string, buildErr?: (err: any) => StatusError, exact?: boolean, user?: string, prefix?: string, token?: string) {
this.buildError = (buildErr ? buildErr : buildError);
this.prefix = (prefix ? prefix : 'Bearer ');
this.user = (user ? user : 'userId');
this.key = (key ? key : 'userType');
this.token = (token ? token : 'token');
this.exact = (exact !== undefined ? exact : true);
this.authorize = this.authorize.bind(this);
}
authorize(v: T[], privilege: string, action?: number): (req: Request, res: Response, next: NextFunction) => void {
return (req: Request, res: Response, next: NextFunction) => {
const data = req.headers['authorization'];
if (data) {
if (!data.startsWith(this.prefix)) {
res.status(401).end(`Authorization must start with '${this.prefix.trim()}'`);
} else {
const token = data.substr(this.prefix.length);
this.verify(token, this.secret).then(payload => {
if (payload === undefined) {
res.status(401).end('Payload cannot be undefined');
} else {
res.locals[this.token] = payload;
const obj = (payload as any)[this.key];
if (!obj) {
res.status(403).end('Payload must contain ' + this.key);
} else {
if (!exist<T>(obj as any, v)) {
res.status(403).end('invalid ' + this.key);
} else {
const userId = (payload as any)[this.user];
if (!userId) {
res.status(403).end('Payload must contain ' + this.user);
} else {
this.privilege(userId, privilege).then(p => {
if (p === none) {
res.status(403).end('no permission for ' + userId);
} else {
if (!action) {
next();
} else {
if (this.exact) {
// tslint:disable-next-line:no-bitwise
const sum = action & p;
if (sum === action) {
return next();
} else {
res.status(403).end('no permission');
}
} else {
if (p >= action) {
return next();
} else {
res.status(403).end('no permission');
}
}
}
}
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
}
}
}
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
} else {
res.status(401).end(`Require 'Authorization' in header`);
}
};
}
}
export class Authorizer<P> {
buildError: (err: any) => StatusError;
key: string;
prefix: string;
exact: boolean;
token: string;
constructor(public secret: string, public verify: (token: string, secret: string) => Promise<P>, public privilege: (userId: string, privilegeId: string) => Promise<number>, buildErr?: (err: any) => StatusError, exact?: boolean, key?: string, prefix?: string, token?: string) {
this.buildError = (buildErr ? buildErr : buildError);
this.prefix = (prefix ? prefix : 'Bearer ');
this.key = (key ? key : 'userId');
this.token = (token ? token : 'token');
this.exact = (exact !== undefined ? exact : true);
this.authorize = this.authorize.bind(this);
}
authorize(privilege: string, action?: number): (req: Request, res: Response, next: NextFunction) => void {
return (req: Request, res: Response, next: NextFunction) => {
const data = req.headers['authorization'];
if (data) {
if (!data.startsWith(this.prefix)) {
res.status(401).end(`Authorization must start with '${this.prefix.trim()}'`);
} else {
const token = data.substr(this.prefix.length);
this.verify(token, this.secret).then(payload => {
if (payload === undefined) {
res.status(401).end('Payload cannot be undefined');
} else {
res.locals[this.token] = payload;
const userId = (payload as any)[this.key];
if (!userId) {
res.status(403).end('Payload must contain ' + this.key);
} else {
this.privilege(userId, privilege).then(p => {
if (p === none) {
res.status(403).end('no permission for ' + userId);
} else {
if (!action) {
next();
} else {
if (this.exact) {
// tslint:disable-next-line:no-bitwise
const sum = action & p;
if (sum === action) {
return next();
} else {
res.status(403).end('no permission');
}
} else {
if (p >= action) {
return next();
} else {
res.status(403).end('no permission');
}
}
}
}
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
}
}).catch(err => {
const { status, body } = this.buildError(err);
res.status(status).end(body);
});
}
} else {
res.status(401).end(`Require 'Authorization' in header`);
}
};
}
}
export const none = 0;
export const read = 1;
export const write = 2;
export const approve = 4;
export const all = 2147483647;
export class PrivilegeLoader {
constructor(public sql: string, public query: <T>(sql: string, args?: any[]) => Promise<T[]>) {
this.privilege = this.privilege.bind(this);
}
privilege(userId: string, privilegeId: string): Promise<number> {
return this.query<any>(this.sql, [userId, privilegeId]).then(v => {
if (!v || v.length === 0) {
return none;
}
const keys = Object.keys(v[0]);
if (keys.length === 0) {
return all;
}
const k: string = keys[0];
let permissions = 0;
let ok = false;
for (const p of v) {
const x = p[k];
if (typeof x === 'number') {
// tslint:disable-next-line:no-bitwise
permissions = permissions | x;
ok = true;
}
}
return (ok ? permissions : all);
});
}
}
export function buildError(err: any): StatusError {
return { status: 401, body: 'Invalid token: ' + toString(err) };
}
export function toString(err: any): string {
return (typeof err === 'string' ? err : JSON.stringify(err));
}