@plumier/jwt
Advanced tools
Comparing version 1.0.0-rc2.59 to 1.0.0
@@ -1,2 +0,2 @@ | ||
import { DefaultFacility, PlumierApplication } from "@plumier/core"; | ||
import { AuthPolicy, Class, DefaultFacility, PlumierApplication, RouteMetadata } from "@plumier/core"; | ||
import KoaJwt from "koa-jwt"; | ||
@@ -6,9 +6,11 @@ export declare type RoleField = string | ((value: any) => Promise<string[]>); | ||
secret?: string; | ||
roleField?: RoleField; | ||
global?: (...args: any[]) => void; | ||
globalAuthorize?: string | string[]; | ||
authPolicies?: Class<AuthPolicy> | Class<AuthPolicy>[] | string | string[]; | ||
} | ||
export declare class JwtAuthFacility extends DefaultFacility { | ||
private option?; | ||
constructor(option?: (JwtAuthFacilityOption & KoaJwt.Options) | undefined); | ||
constructor(option?: (JwtAuthFacilityOption & Partial<KoaJwt.Options>) | undefined); | ||
preInitialize(app: Readonly<PlumierApplication>): Promise<void>; | ||
initialize(app: Readonly<PlumierApplication>, routes: RouteMetadata[]): Promise<void>; | ||
setup(app: Readonly<PlumierApplication>): void; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.JwtAuthFacility = void 0; | ||
const tslib_1 = require("tslib"); | ||
const core_1 = require("@plumier/core"); | ||
const koa_jwt_1 = tslib_1.__importDefault(require("koa-jwt")); | ||
const path_1 = require("path"); | ||
const query_parser_1 = require("@plumier/query-parser"); | ||
async function getPoliciesByFile(root, opt) { | ||
if (Array.isArray(opt)) { | ||
const result = []; | ||
for (const o of opt) { | ||
result.push(...await getPoliciesByFile(root, o)); | ||
} | ||
return result; | ||
} | ||
if (typeof opt === "string") { | ||
const path = path_1.isAbsolute(opt) ? opt : path_1.join(root, opt); | ||
const result = await core_1.findClassRecursive(path); | ||
return getPoliciesByFile(root, result.map(x => x.type)); | ||
} | ||
else { | ||
return opt.name.match(/policy$/i) ? [opt] : []; | ||
} | ||
} | ||
// --------------------------------------------------------------------- // | ||
// --------------------------- TOKEN RESOLVER -------------------------- // | ||
// --------------------------------------------------------------------- // | ||
function resolveAuthorizationHeader(ctx, opts) { | ||
if (!ctx.header || !ctx.header.authorization) | ||
return; | ||
const parts = ctx.header.authorization.trim().split(' '); | ||
if (parts.length === 2) { | ||
const scheme = parts[0]; | ||
const credentials = parts[1]; | ||
if (/^Bearer$/i.test(scheme)) { | ||
return credentials; | ||
} | ||
} | ||
throw new Error("Only Bearer authorization scheme supported"); | ||
} | ||
function resolveCookies(ctx, opts) { | ||
return opts.cookie && ctx.cookies.get(opts.cookie); | ||
} | ||
function getToken(ctx, opts) { | ||
var _a; | ||
return (_a = resolveAuthorizationHeader(ctx, opts)) !== null && _a !== void 0 ? _a : resolveCookies(ctx, opts); | ||
} | ||
/* ------------------------------------------------------------------------------- */ | ||
@@ -14,15 +57,36 @@ /* --------------------------- MAIN IMPLEMENTATION ------------------------------- */ | ||
} | ||
async preInitialize(app) { | ||
var _a, _b, _c, _d; | ||
// set auth policies | ||
const defaultPolicies = [core_1.PublicAuthPolicy, core_1.AuthenticatedAuthPolicy, core_1.ReadonlyAuthPolicy, core_1.WriteonlyAuthPolicy]; | ||
const defaultPath = [require.main.filename, "./**/*policy.+(ts|js)", "./**/*controller.+(ts|js)", "./**/*entity.+(ts|js)"]; | ||
const configPolicies = core_1.globalPolicies.concat(await getPoliciesByFile(app.config.rootDir, (_b = (_a = this.option) === null || _a === void 0 ? void 0 : _a.authPolicies) !== null && _b !== void 0 ? _b : defaultPath)); | ||
const authPolicies = [...defaultPolicies, ...configPolicies]; | ||
app.set({ authPolicies }); | ||
// analyze auth policies and throw error | ||
core_1.analyzeAuthPolicyNameConflict(authPolicies); | ||
// set auth policies analyzers | ||
const defaultAnalyzers = (_c = app.config.analyzers) !== null && _c !== void 0 ? _c : []; | ||
const policies = authPolicies.map(x => new x()); | ||
const authorizeAnalyzer = core_1.createAuthorizationAnalyzer(policies, (_d = this.option) === null || _d === void 0 ? void 0 : _d.globalAuthorize); | ||
const queryParserAnalyzer = query_parser_1.createQueryParserAnalyzer(policies); | ||
app.set({ analyzers: [...defaultAnalyzers, ...authorizeAnalyzer, ...[queryParserAnalyzer]] }); | ||
} | ||
async initialize(app, routes) { | ||
// show authorization applied for route analysis | ||
core_1.updateRouteAuthorizationAccess(routes, app.config); | ||
} | ||
setup(app) { | ||
var _a, _b, _c, _d; | ||
Object.assign(app.config, { | ||
enableAuthorization: true, | ||
roleField: ((_a = this.option) === null || _a === void 0 ? void 0 : _a.roleField) || "role", | ||
globalAuthorizationDecorators: (_b = this.option) === null || _b === void 0 ? void 0 : _b.global | ||
}); | ||
const secret = (_d = (_c = this.option) === null || _c === void 0 ? void 0 : _c.secret) !== null && _d !== void 0 ? _d : process.env.PLUM_JWT_SECRET; | ||
var _a, _b, _c; | ||
app.set({ enableAuthorization: true }); | ||
// global authorization if not defined then its Authenticated by default | ||
const globalAuthorizations = !((_a = this.option) === null || _a === void 0 ? void 0 : _a.globalAuthorize) || this.option.globalAuthorize.length === 0 ? [core_1.Authenticated] : this.option.globalAuthorize; | ||
app.set({ globalAuthorizations }); | ||
app.set({ openApiSecuritySchemes: { bearer: { type: "http", scheme: "bearer", bearerFormat: "JWT" } } }); | ||
const secret = (_c = (_b = this.option) === null || _b === void 0 ? void 0 : _b.secret) !== null && _c !== void 0 ? _c : process.env.PLUM_JWT_SECRET; | ||
if (!secret) | ||
throw new Error("JWT Secret not provided. Provide secret on JwtAuthFacility constructor or environment variable PLUM_JWT_SECRET"); | ||
app.koa.use(koa_jwt_1.default(Object.assign(Object.assign({ cookie: "Authorization" }, this.option), { secret, passthrough: true }))); | ||
app.koa.use(koa_jwt_1.default(Object.assign(Object.assign({ cookie: "Authorization", getToken }, this.option), { secret, passthrough: true }))); | ||
} | ||
} | ||
exports.JwtAuthFacility = JwtAuthFacility; |
{ | ||
"name": "@plumier/jwt", | ||
"version": "1.0.0-rc2.59+f9a4594", | ||
"version": "1.0.0", | ||
"description": "Plumier authorization module using JWT", | ||
@@ -8,3 +8,4 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"compile": "tsc -p tsconfig.build.json" | ||
"compile": "tsc -p tsconfig.build.json", | ||
"copy-readme": "cpy ../../readme.md ." | ||
}, | ||
@@ -14,5 +15,9 @@ "author": "Ketut Sandiarsa", | ||
"dependencies": { | ||
"@plumier/core": "1.0.0-rc2.59+f9a4594", | ||
"koa-jwt": "^3.6.0" | ||
"@plumier/core": "^1.0.0", | ||
"@plumier/query-parser": "^1.0.0", | ||
"koa-jwt": "^4.0.1" | ||
}, | ||
"devDependencies": { | ||
"cpy-cli": "^3.1.1" | ||
}, | ||
"bugs": { | ||
@@ -28,3 +33,3 @@ "url": "https://github.com/plumier/plumier/issues" | ||
}, | ||
"gitHead": "f9a4594cd6e1122b939263e24803d7c3c761eee7" | ||
"gitHead": "7e8d4af7b6163780d960e5820e5dc469878e93bd" | ||
} |
204
readme.md
# Plumier | ||
Delightful Node.js Rest Framework | ||
[![Build Status](https://travis-ci.org/plumier/plumier.svg?branch=master)](https://travis-ci.org/plumier/plumier) | ||
[![Build status](https://ci.appveyor.com/api/projects/status/6carp7h4q50v4pj6?svg=true)](https://ci.appveyor.com/project/ktutnik/plumier-isghw) | ||
[![Build Status](https://github.com/plumier/plumier/workflows/ubuntu/badge.svg)](https://github.com/plumier/plumier/actions?query=workflow%3Aubuntu) | ||
[![Build status](https://github.com/plumier/plumier/workflows/windows/badge.svg)](https://github.com/plumier/plumier/actions?query=workflow%3Awindows) | ||
[![Coverage Status](https://coveralls.io/repos/github/plumier/plumier/badge.svg?branch=master)](https://coveralls.io/github/plumier/plumier?branch=master) | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/plumier/plumier.svg)](https://greenkeeper.io/) | ||
[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lernajs.io/) | ||
![npm (canary)](https://img.shields.io/npm/v/plumier/canary) | ||
![npm (latest)](https://img.shields.io/npm/v/plumier/latest) | ||
[![npm](https://img.shields.io/npm/v/plumier/canary)](https://www.npmjs.com/package/plumier?activeTab=versions) | ||
[![npm](https://img.shields.io/npm/v/plumier/latest)](https://www.npmjs.com/package/plumier?activeTab=versions) | ||
## Motivation | ||
## Documentation | ||
Read the project documentation on https://plumierjs.com | ||
My subjective opinion about TypeScript frameworks nowadays is most of them are too advanced, even to start a very simple API you need to prepare yourself with some advanced knowledge of Separation of Concern, Dependency Injection, SOLID principle and many other design pattern and best practices comes from Object Oriented world. | ||
Most of those frameworks take advantage of OO fanciness, where framework provided a mandatory rule on how you should separate your logic and layout your source code to keep it clean and SOLID. | ||
In the other hands frameworks doesn't put robustness and secureness as priority because with its fancy separation you can create your own implementation of type conversion, validator or authorization on top of an existing library such as Joi and Passport. | ||
### What About Express? | ||
I am a big fans of Express, I spent years developing API using Express. Good parts about Express is its simplicity, its easy to master Express only by reading the documentation or by spending a 10 minutes tutorial. Because its only consist of Routing and middleware. | ||
Bad things about Express is its getting harder when you get into the detail. By default express doesn't have a built-in type conversion validator and authorization functionalities. Thus you need to combine some npm packages to do the detail things. You need to configure schema for joi validation and mongoose, setting this and that for authorization, at the end your code is so far from simple. | ||
## Enter Plumier | ||
Welcome to Plumier where robustness and secureness is mandatory and fanciness is optional. Unlike most TypeScript framework Plumier focus on development happiness and productivity while keep simplest implementation robust and secure. | ||
The main goal is to make your development time fast and delightful by providing built-in functionalities such as automatic data type conversion, comprehensive list (40+ types) of validator, authorization to programmatically restrict access to some endpoints and more cool features such as: | ||
* [Parameter binding](https://plumierjs.com/docs/refs/parameter-binding) | ||
* [Route generation](https://plumierjs.com/docs/refs/route) | ||
* [Static route generation analysis](https://plumierjs.com/docs/refs/static-analysis) | ||
* [Meta programming on middleware basis](https://medium.com/hackernoon/adding-an-auditing-system-into-a-rest-api-4fbb522240ea) | ||
* API versioning based on reflection | ||
All above features created with dedicated reflection library to possibly perform rich meta programming on top of TypeScript language to make everything feel more automatic with less configurations. | ||
Furthermore Plumier doesn't force you to follow some design pattern or best practice. Plumier application is highly configurable that make you able to layout your source code freely. | ||
### Robust and Secure | ||
Plumier provided some built-in functionalities that work in the background to make the most trivial implementation keep secure and robust. | ||
```typescript | ||
class AnimalsController { | ||
@route.get() | ||
list(offset:number, @val.int({ min: 1 }) limit:number) { | ||
//implementation | ||
} | ||
} | ||
``` | ||
Above controller generate single endpoints `GET /animals/list?offset=0&list=10`. Plumier uses a dedicated type introspection (reflection) library to make it able to extract TypeScript type annotation than translate it into metadata and provide functionalities that working on the background. | ||
1. It automatically bound `offset` and `limit` parameter with request query by name, no further configuration needed. | ||
2. It automatically convert the request query `offset` and `limit` value into appropriate parameter data type. This function prevent bad user submitting bad value causing conversion error or even sql injection. | ||
3. It automatically validate the `limit` parameter and make sure if the provided value is a positive number. | ||
4. It taking care of query case insensitivity, `GET /animals/list?OFFSET=0&LIMIT=10` will keep working. Note that query is case sensitive in most frameworks. | ||
Plumier has [comprehensive list](refs/validation#decorators) of decorator based validators, it easily can be applied on method parameters or domain model properties. | ||
```typescript | ||
@domain() | ||
class User { | ||
constructor( | ||
@val.length({ min: 5, max: 128 }) | ||
public name: string, | ||
@val.email() | ||
public email: string, | ||
@val.before() | ||
public dateOfBirth: Date, | ||
public active: boolean | ||
) { } | ||
} | ||
``` | ||
Furthermore Plumier provided built-in decorator based authorization to easily restrict access to your API endpoints. | ||
```typescript | ||
class UsersController { | ||
// GET /users?offset&limit | ||
// only accessible by Admin | ||
@authorize.role("Admin") | ||
@route.get("") | ||
list(offset:number, limit:number) { } | ||
// POST /users | ||
// accessible by public | ||
@authorize.public() | ||
@route.post("") | ||
save(data:User){} | ||
} | ||
``` | ||
Above code showing that some authorization decorator applied to the method to restrict access to each endpoint handled by controller's method. | ||
### Useful Reflection Based Helpers | ||
Another benefit of using reflection library is Plumier able to provided an official [Mongoose](https://mongoosejs.com/) helper to automatically generate mongoose schema from domain model. | ||
```typescript | ||
import { collection, model } from "@plumier/mongoose" | ||
// mark domain model as collection | ||
@collection() | ||
class User { | ||
constructor( | ||
public name:string, | ||
public email:string, | ||
public dateOfBirth:Date, | ||
public role: "Admin" | "User", | ||
public active:boolean | ||
){} | ||
} | ||
// model() function automatically generate Mongoose schema | ||
// based on User properties | ||
const UserModel = model(User) | ||
``` | ||
Read more information about Mongoose helper [here](refs/mongoose-helper). | ||
### Reduce Duplication | ||
There is a best practice spread among static type programmers: **Never use your domain model as DTO**. Literally its a good advice because in a common framework using domain model as DTO can lead to some security issue, but this will ends up in another issue: bloated code and duplication. | ||
Plumier provided an advanced authorization functionalities which enables you to restrict write some property of request body by providing `@authorize` decorator on the domain model. | ||
```typescript | ||
import { authorize } from "plumier" | ||
import { collection } from "@plumier/mongoose" | ||
@collection() | ||
class User { | ||
constructor( | ||
public name:string, | ||
public email:string, | ||
public dateOfBirth:Date, | ||
//restrict access only to Admin | ||
@authorize.role("Admin") | ||
public role: "Admin" | "User", | ||
public active:boolean | ||
){} | ||
} | ||
``` | ||
Using above code, only user with `Admin` role will be able to set the `role` property. Using this functionalities will cut a lot of bloated DTO classes and duplication, and make the security aspect of the application easily reviewed. | ||
### Lightweight | ||
Above all, with all those features above, Plumier is a lightweight framework. | ||
``` | ||
GET method benchmark starting... | ||
Server Base Method Req/s Cost (%) | ||
koa GET 32566.55 0.00 | ||
plumier koa GET 31966.55 1.84 | ||
express GET 19047.60 0.00 | ||
nest express GET 16972.91 10.89 | ||
loopback express GET 3719.80 80.47 | ||
POST method benchmark starting... | ||
Server Base Method Req/s Cost (%) | ||
koa POST 12651.46 0.00 | ||
plumier koa POST 11175.10 11.67 | ||
express POST 9521.28 0.00 | ||
nest express POST 5251.00 44.85 | ||
loopback express POST 2294.00 75.91 | ||
``` | ||
Above is a full stack benchmark (routing, body parser, validator, type conversion) result of Plumier and other TypeScript framework. Showing that using Plumier is as fast as using Koa. The benchmark source code can be found [here](https://github.com/ktutnik/full-stack-benchmarks). | ||
Creating lightweight framework is not easy, from result above Plumier only 1.84% slower than Koa (its base framework) in the other hand Nest 10.89% and Loopback 4 is 80% slower than their base framework. | ||
## Requirements | ||
* Node.js >= 10.0.0 | ||
* TypeScript | ||
## Blog Posts and Publications | ||
* [Reason, motivation and how Plumier designed](https://medium.com/hackernoon/i-spent-a-year-to-reinvent-a-node-js-framework-b3b0b1602ad5) | ||
* [How to use Plumier with mongoose](https://hackernoon.com/create-secure-restful-api-with-plumier-and-mongoose-3ngz32lu) | ||
* [Advanced usage of Plumier middleware to perform AOP and metaprogramming](https://hackernoon.com/adding-an-auditing-system-into-a-rest-api-4fbb522240ea) | ||
## Documentation | ||
Go to Plumier [documentation](https://plumierjs.com) for complete documentation and tutorial | ||
## Tutorials | ||
* [Basic REST api tutorial using Knex.js](https://plumierjs.com/docs/tutorials/basic-sql/get-started) | ||
## Examples | ||
* [Basic REST API with Knex.js](https://github.com/plumier/tutorial-todo-sql-backend) | ||
* [Basic REST api with Mongoose](https://github.com/plumier/tutorial-todo-mongodb-backend) | ||
* [Plumier - React - Monorepo - Social Login](https://github.com/plumier/tutorial-monorepo-social-login) | ||
* [Plumier - Vue.js - Monorepo - Social Login](https://github.com/plumier/tutorial-social-login-vue) | ||
* [Plumier - React Native - Monorepo](https://github.com/plumier/tutorial-todo-monorepo-react-native) | ||
## Contributing | ||
@@ -205,6 +19,6 @@ To run Plumier project on local machine, some setup/app required | ||
* Visual Studio Code (Recommended) | ||
* Yarn `npm install -g yarn` | ||
* Yarn (required) | ||
### Local Setup | ||
* Fork and clone the project | ||
* Fork and clone the project `git clone` | ||
* Install dependencies by `yarn install` | ||
@@ -215,6 +29,4 @@ * Run test by `yarn test` | ||
Plumier already provided vscode `task` and `launch` setting. To start debugging a test scenario: | ||
* Build the project | ||
* Locate the test file and narrow the test runs by using `.only` | ||
* Put breakpoint on any location you need on `.ts` file | ||
* On start/debug configuration select `Jest Current File` and start debugging | ||
* Process will halt properly on the `.ts` file. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
106
1
0
8924
3
1
31
+ Added@plumier/query-parser@^1.0.0
+ Added@plumier/core@1.1.3(transitive)
+ Added@plumier/reflect@1.1.3(transitive)
+ Added@plumier/validator@1.1.3(transitive)
+ Added@types/accepts@1.3.7(transitive)
+ Added@types/acorn@4.0.6(transitive)
+ Added@types/body-parser@1.19.5(transitive)
+ Added@types/connect@3.4.38(transitive)
+ Added@types/content-disposition@0.5.8(transitive)
+ Added@types/cookies@0.9.0(transitive)
+ Added@types/debug@4.1.12(transitive)
+ Added@types/estree@1.0.6(transitive)
+ Added@types/express@5.0.0(transitive)
+ Added@types/express-serve-static-core@5.0.2(transitive)
+ Added@types/glob@7.2.0(transitive)
+ Added@types/http-assert@1.5.6(transitive)
+ Added@types/http-errors@2.0.4(transitive)
+ Added@types/keygrip@1.0.6(transitive)
+ Added@types/koa@2.15.0(transitive)
+ Added@types/koa-compose@3.2.8(transitive)
+ Added@types/mime@1.3.5(transitive)
+ Added@types/minimatch@5.1.2(transitive)
+ Added@types/ms@0.7.34(transitive)
+ Added@types/node@22.10.2(transitive)
+ Added@types/qs@6.9.17(transitive)
+ Added@types/range-parser@1.2.7(transitive)
+ Added@types/send@0.17.4(transitive)
+ Added@types/serve-static@1.15.7(transitive)
+ Added@types/validator@13.12.2(transitive)
+ Addedacorn@8.8.1(transitive)
+ Addedaggregate-error@3.1.0(transitive)
+ Addedansi-styles@4.3.0(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedchalk@4.1.2(transitive)
+ Addedclean-stack@2.2.0(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedindent-string@4.0.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedjsonwebtoken@9.0.2(transitive)
+ Addedkoa-jwt@4.0.4(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedp-any@2.1.0(transitive)
+ Addedp-cancelable@2.1.1(transitive)
+ Addedp-some@4.1.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-to-regexp@6.3.0(transitive)
+ Addedreflect-metadata@0.1.14(transitive)
+ Addedsemver@7.6.3(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedtslib@2.8.1(transitive)
+ Addedtype-fest@0.3.1(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addedvalidator@13.12.0(transitive)
+ Addedwrappy@1.0.2(transitive)
- Removedaggregate-error@1.0.0(transitive)
- Removedclean-stack@1.3.0(transitive)
- Removedindent-string@3.2.0(transitive)
- Removedjsonwebtoken@8.5.1(transitive)
- Removedkoa-jwt@3.6.0(transitive)
- Removedp-any@1.1.0(transitive)
- Removedp-some@2.0.1(transitive)
- Removedsemver@5.7.2(transitive)
Updated@plumier/core@^1.0.0
Updatedkoa-jwt@^4.0.1