security-express
Version:
security-express
107 lines (103 loc) • 3.51 kB
text/typescript
import { NextFunction, Request, Response } from "express"
export type Handle = (req: Request, res: Response, next: NextFunction) => void
export type Authorize = (privilege: string, action?: number) => Handle
export interface SimpleMap {
[key: string]: string | number | boolean | Date
}
// tslint:disable-next-line:max-classes-per-file
export class Authorizer {
protected userId: string
protected permissions: string
constructor(
protected privilege: (userId: string, privilegeId: string) => Promise<number>,
protected logError: (msg: string, m?: SimpleMap, ctx?: any) => void,
protected exact?: boolean,
userId?: string,
permissions?: string,
) {
this.userId = userId ? userId : "userId"
this.permissions = permissions ? permissions : "permissions"
this.exact = exact !== undefined ? exact : true
this.authorize = this.authorize.bind(this)
}
authorize(privilege: string, action?: number): Handle {
return (req: Request, res: Response, next: NextFunction) => {
const userId = res.locals[this.userId]
if (!userId) {
res.status(401).end("Authentication is required or session has expired.")
} else {
this.privilege(userId, privilege)
.then((p) => {
if (p === none) {
res.status(403).end("no permission for " + userId)
} else {
res.locals[this.permissions] = p
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) => {
this.logError(`Error in privilege check for user ${userId} with privilege ${privilege}. Details: ${toString(err)}`)
res.status(403).end("no permission")
})
}
}
}
}
export const none = 0
export const read = 1
export const write = 2
export const approve = 4
export const all = 2147483647
// tslint:disable-next-line:max-classes-per-file
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 toString(err: any): string {
return typeof err === "string" ? err : JSON.stringify(err)
}