UNPKG

security-express

Version:
410 lines (288 loc) 7.85 kB
# security-express A lightweight authorization middleware for **Express** applications. `security-express` provides a simple, flexible authorization layer based on middleware and dependency injection. It does not dictate how users authenticate or where permissions are stored, allowing it to integrate seamlessly with existing authentication systems and data sources. ## Features * Express-native middleware * Dependency injection for permission resolution * Supports bitmask permissions * Supports hierarchical permission levels * Database-agnostic * Framework-independent permission loading * Zero runtime dependencies (other than Express) * Small, composable API ## Installation ```bash npm install security-express ``` or ```bash yarn add security-express ``` ## Request flow ```text Auth middleware sets res.locals.userId authorize(\"invoice\", write) calls privilege(userId, \"invoice\") returns permission bitmask (e.g. 3 = read|write) If (action & p) === action next(), else 403 ``` ## Concepts The library separates authorization into two responsibilities: * **Authorizer** Express middleware that enforces permissions. * **PrivilegeLoader** Helper that loads user permissions from a data source. Authentication is intentionally outside the scope of this library. ``` Authentication res.locals.userId Authorizer privilege(userId, privilege) Database / API / Redis / LDAP / ... ``` ## Examples - [cms-backoffice](https://github.com/content-system/cms-backoffice): A backoffice microservice for a CMS (user, role, audit-log, category, content, job) - [admin-service](https://github.com/fintech-product/admin-service): A backoffice microservice for a common fintech product (user, role, audit-log, currency, country, locale) ## Basic Usage ```typescript import express from "express"; import { Authorizer, PrivilegeLoader, read, write} from "security-express"; const app = express(); const loader = new PrivilegeLoader( ` SELECT permission FROM user_privileges WHERE user_id = ? AND privilege = ? `, query ); const authorizer = new Authorizer(loader.privilege, console.error); app.get( "/users", authorizer.authorize("user", read), (req, res) => { res.send("Allowed"); } ); app.post( "/users", authorizer.authorize("user", write), (req, res) => { res.send("Created"); } ); ``` --- # Authorizer `Authorizer` creates Express middleware that verifies whether the current user has sufficient permissions. ```typescript const authorizer = new Authorizer(privilege, logError); ``` ### Constructor ```typescript new Authorizer( privilege, logError, exact?, userId?, permissions? ) ``` | Parameter | Description | | ------------- | ---------------------------------------------------------------------------- | | `privilege` | Function that returns a user's permission value. | | `logError` | Error logging callback. | | `exact` | Permission comparison mode. Defaults to `true`. | | `userId` | Key used in `res.locals` for the authenticated user id. Default: `"userId"`. | | `permissions` | Key used to store resolved permissions. Default: `"permissions"`. | --- ## authorize() ```typescript authorizer.authorize(privilege, action?) ``` Returns an Express middleware. ```typescript app.get( "/orders", authorizer.authorize("order", read), handler ); ``` If `action` is omitted, the middleware only verifies that the user possesses the specified privilege. --- # Permission Models ## Bitmask Permissions (Default) The default mode uses bitwise permission flags. ```typescript import { read, write, approve } from "security-express"; ``` | Permission | Value | | ---------- | ---------: | | `none` | 0 | | `read` | 1 | | `write` | 2 | | `approve` | 4 | | `all` | 2147483647 | Permissions may be combined. ```typescript const permission = read | write; ``` Checking permissions: ```typescript authorizer.authorize("invoice", write); ``` A user with ``` read | write ``` is authorized. --- ## Hierarchical Permissions If permissions represent levels instead of flags, set `exact` to `false`. ```typescript const authorizer = new Authorizer(privilege, logError, false); ``` Example: | Level | Meaning | | ----: | ------------- | | 1 | Viewer | | 2 | Editor | | 3 | Manager | | 4 | Administrator | A user with level `4` automatically satisfies checks for levels `1`, `2`, and `3`. --- # PrivilegeLoader `PrivilegeLoader` is a helper that converts database results into permission values. ```typescript const loader = new PrivilegeLoader(sql, query); ``` ### Constructor ```typescript new PrivilegeLoader(sql, query) ``` Where ```typescript query<T>(sql, args): Promise<T[]> ``` can be implemented using any database library. Example: ```typescript const loader = new PrivilegeLoader( ` SELECT permission FROM user_privileges WHERE user_id = ? AND privilege = ? `, db.query ); ``` The loader combines multiple rows using bitwise OR. For example: | Row | | --: | | 1 | | 2 | | 4 | becomes ``` 7 ``` --- # Custom Permission Provider Instead of using SQL, permissions may come from any source. ```typescript const authorizer = new Authorizer( async (userId, privilege) => { return permissionService.getPermission( userId, privilege ); }, console.error ); ``` Possible sources include: * SQL databases * MongoDB * Redis * REST APIs * GraphQL * LDAP * Active Directory --- # Authentication Authentication is intentionally not included. Populate `res.locals.userId` before calling the authorization middleware. Example: ```typescript app.use(jwtAuthentication); app.use(authorizer.authorize("invoice", read)); ``` --- # Error Responses The middleware returns: | Status | Description | | -----: | ------------------------------------------- | | 401 | User is not authenticated. | | 403 | User does not have the required permission. | --- # API ## Types ```typescript type Handle ``` Express middleware. ```typescript type Authorize ``` Authorization middleware factory. ```typescript interface SimpleMap ``` Simple key/value map used for logging. --- ## Constants ```typescript none read write approve all ``` --- ## Exports ```typescript Authorizer PrivilegeLoader none read write approve all toString Handle Authorize SimpleMap ``` --- # Design Philosophy `security-express` focuses on one responsibility: > Determine whether a request is authorized. It deliberately avoids implementing authentication, session management, JWT validation, OAuth, or user management. Those concerns belong to the application. By keeping authorization independent from authentication and storage, the library remains lightweight, composable, and easy to integrate into existing Express applications. --- # License MIT