-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
534 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# webstorm settings | ||
.idea | ||
|
||
# Logs | ||
logs | ||
*.log | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# express-apigw-base-plugin | ||
|
||
This plugin adds custom policies to the Express Gateway. For more information about Express Gateway plug-ins, see the documentation [Express Gateway - Plugins](https://www.express-gateway.io/docs/plugins/) | ||
|
||
### How to manually install | ||
1. Execute: | ||
``` | ||
npm install https://github.com/nutes-uepb/express-apigw-base-plugin --save | ||
``` | ||
2. Add the installed plugin to the gateway system settings in the file "system.config.yml". As in the example below: | ||
|
||
```javascript | ||
plugins: | ||
express-apigw-base-plugin: | ||
package: 'express-apigw-base-plugin/manifest.js' | ||
``` | ||
4. Okay, done that it is already possible to use the policies in "gateway.config.yml" | ||
|
||
### How to install automatically | ||
|
||
Plugins are bundled as Node modules and distributed through npm. The Express Gateway CLI is used to install and configure plugins. | ||
Installed plugins are declared in the system.config.yml and are then ready to be used. Express Gateway CLI is a convenient way to install and enable plugins. | ||
```bash | ||
$ eg plugin install https://github.com/nutes-uepb/express-apigw-base-plugin | ||
``` | ||
|
||
### Description of policies | ||
|
||
* body-parser-policy: Converts the request into an object using the body-parser. | ||
+ No configuration parameters | ||
* auth-policy: Performs authentication and then validates the consumer at the express gateway. | ||
+ Configuration parameters: | ||
- urlauthservice: Indicates authentication service url. (ie 'http://localhost:5000/api/users/auth') | ||
- secretOrPublicKey: String with the secret to validate JWT token. (ie 'mysecret') | ||
- secretOrPublicKeyFile: File with the secret to validate JWT token. (ie 'key.pem') | ||
- issuer: Valid JWT token issuer. (ie 'myapp') | ||
* jwt-policy: Validates token jwt based on expiration time and emitter. In addition to verifying the existence of the respective consumer. NOTE: In the case of the jwt secret, it can be informed using one of two properties: "secretOrPublicKey" or "secretOrPublicKeyFile". | ||
+ Configuration parameters: | ||
- secretOrPublicKey: String with the secret to validate JWT token. (ie 'mysecret') | ||
- secretOrPublicKeyFile: File with the secret to validate JWT token. (ie 'key.pem') | ||
- issuer: Valid JWT token issuer. (ie 'myapp') | ||
* jwtScopes-policy: Performs the validation of the necessary scopes in the requests. Remembering that each scope must be configuring in each apiPublica path. In case of multiple scopes, the policy verifies the existence of at least one of the scopes. | ||
+ No configuration parameters |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/** | ||
* Condition to verify that the requested meets the regular expression and method configured. | ||
* Only the regexpath parameter is required. | ||
*/ | ||
module.exports = { | ||
name: 'regex-path-method', | ||
handler: function (req, conditionConfig) { | ||
const regex = new RegExp(conditionConfig.regexpath); | ||
if (conditionConfig.method) { | ||
return (regex.test(req.url) && req.method === conditionConfig.method); | ||
} | ||
return regex.test(req.url); | ||
}, | ||
schema: { | ||
$id: 'http://express-gateway.io/schemas/conditions/regex-path-method.json', | ||
type: 'object', | ||
properties: { | ||
regexPath: { | ||
type: 'string' | ||
}, | ||
method: { | ||
type: 'string' | ||
} | ||
}, | ||
required: ['regexPath'] | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
'use strict' | ||
|
||
/** | ||
* Plug-in export file | ||
* Where all plug-in policies, conditions, and routes are logged | ||
*/ | ||
const fs = require('fs') | ||
const apiReferencePathFile = process.env.API_REFERENCE_PATH_FILE | ||
module.exports = { | ||
version: '1.0.1', | ||
init: function (pluginContext) { | ||
pluginContext.registerPolicy(require('./policies/authentication/jwt-policy')) | ||
pluginContext.registerPolicy(require('./policies/authorization/jwt-scopes-policy')) | ||
pluginContext.registerPolicy(require('./policies/auth/auth-policy')) | ||
pluginContext.registerPolicy(require('./policies/body-parser/body-parser-policy')) | ||
pluginContext.registerPolicy(require('./policies/delete-user/delete-user-policy')) | ||
pluginContext.registerCondition(require('./conditions/regex-path-method')) | ||
pluginContext.registerGatewayRoute(require('./routes/middlewares')) | ||
if (fs.existsSync(apiReferencePathFile)) { | ||
pluginContext.registerGatewayRoute(require(apiReferencePathFile)) | ||
} | ||
}, | ||
// this is for CLI to automatically add to "policies" whitelist in gateway.config | ||
policies: ['jwt-policy', 'jwtScopes-policy', 'auth-policy', 'body-parser-policy', 'delete-user-policy'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "express-apigw-base-plugin", | ||
"version": "1.0.0", | ||
"description": "Express Gateway Base Plugin", | ||
"main": "manifest.js", | ||
"author": "NUTES/UEPB", | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"axios": "^0.19.0", | ||
"cors": "^2.8.5", | ||
"body-parser": "^1.19.0", | ||
"jsonwebtoken": "^8.5.1", | ||
"passport": "^0.4.0", | ||
"passport-jwt": "^4.0.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.2.0", | ||
"mocha": "^6.2.0", | ||
"sinon": "^7.3.2", | ||
"supertest": "^4.0.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** | ||
* Login Policy | ||
* | ||
*/ | ||
module.exports = { | ||
name: 'auth-policy', | ||
policy: require('./auth'), | ||
schema: { | ||
name: 'auth-policy', | ||
$id: 'http://express-gateway.io/schemas/policies/auth-policy.json', | ||
type: 'object', | ||
properties: { | ||
urlAuthService: { | ||
type: 'string' | ||
}, | ||
secretOrPublicKey: { | ||
type: 'string' | ||
}, | ||
secretOrPublicKeyFile: { | ||
type: 'string' | ||
}, | ||
issuer: { | ||
type: 'string' | ||
} | ||
}, | ||
required: ['urlAuthService', 'issuer'], | ||
oneOf: [{required: ['secretOrPublicKey']}, {required: ['secretOrPublicKeyFile']}] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/** | ||
* Login Policy | ||
*/ | ||
const jwt = require('jsonwebtoken') | ||
const fs = require('fs') | ||
let userService = require('../../services/user-service') | ||
let gatewayService = require('express-gateway/lib/services') | ||
|
||
module.exports = function (actionParams, authServiceTest, localServicesTest) { | ||
return async (req, res, next) => { | ||
// Test context, Mocks authService and services. | ||
if (authServiceTest && localServicesTest) { | ||
userService = authServiceTest | ||
gatewayService = localServicesTest | ||
} | ||
|
||
try { | ||
// 1. Run authentication on the account-service | ||
const authResponse = await userService.auth(actionParams.urlAuthService, req.body) | ||
const accessToken = authResponse.data.access_token | ||
|
||
// 2. Read JWT public key | ||
const secretOrKey = actionParams.secretOrPublicKeyFile ? | ||
fs.readFileSync(actionParams.secretOrPublicKeyFile, 'utf8') : actionParams.secretOrPublicKey | ||
|
||
// 3. Checks whether the token obtained matches the public key. | ||
const decode = await jwt.verify(accessToken, secretOrKey, { | ||
algorithms: ['RS256'], | ||
issuer: actionParams.issuer | ||
}) | ||
// 3.1 Checks if the user ID to whom the token belongs is present. 'sub' is required. | ||
if (!decode.sub) throw new Error('UNAUTHORIZED') | ||
|
||
// 4. Get user in local database. | ||
// If the user exists, there is nothing else to do. | ||
if (await gatewayService.user.find(decode.sub)) return res.status(200).send(authResponse.data) | ||
|
||
// 5. If the login was successful and the user is not yet registered at the local bank, the same is created. | ||
const userGateway = await gatewayService.user.insert({username: decode.sub}) | ||
if (!userGateway) throw new Error('INTERNAL_ERROR') | ||
|
||
return res.status(200).send(authResponse.data) | ||
} catch (err) { | ||
/* Report in logs that JWT has an invalid signature */ | ||
if (err.name === 'JsonWebTokenError' && err.message === 'invalid signature') { | ||
console.error(`[auth-policy] error: Invalid JWT Signature!`) | ||
} | ||
if (err.response && err.response.data) { | ||
return res.status(err.response.status).send(err.response.data) | ||
} | ||
|
||
if (err.message === 'UNAUTHORIZED') return res.status(401).send(handlerMessageError(401)) | ||
return res.status(500).send(handlerMessageError(500)) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Handler of general error messages. | ||
* | ||
* @param code | ||
* @returns {{code: number, message: string, description: string}} | ||
*/ | ||
function handlerMessageError(code) { | ||
if (code === 401) return { | ||
'code': 401, | ||
'message': 'UNAUTHORIZED', | ||
'description': 'The token user is not properly registered as a consumer at the gateway.' | ||
} | ||
|
||
return { | ||
'code': 500, | ||
'message': 'INTERNAL SERVER ERROR', | ||
'description': 'An internal server error has occurred.' | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Policy to validate JWT | ||
*/ | ||
module.exports = { | ||
name: 'jwt-policy', | ||
policy: require('./jwt'), | ||
schema: { | ||
$id: 'http://express-gateway.io/schemas/policies/jwt-policy.json', | ||
type: 'object', | ||
properties: { | ||
secretOrPublicKey: { | ||
type: 'string' | ||
}, | ||
secretOrPublicKeyFile: { | ||
type: 'string' | ||
}, | ||
issuer: { | ||
type: 'string' | ||
} | ||
}, | ||
required: ['issuer'], | ||
oneOf: [{required: ['secretOrPublicKey']}, {required: ['secretOrPublicKeyFile']}] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
'use strict' | ||
/** | ||
* Policy to validate JWT | ||
*/ | ||
const passportJWT = require('passport-jwt') | ||
const fs = require('fs') | ||
let passport = require('passport') | ||
let gatewayService = require('express-gateway/lib/services') | ||
|
||
const JWTStrategy = passportJWT.Strategy | ||
const ExtractJWT = passportJWT.ExtractJwt | ||
|
||
module.exports = function (actionParams, testContext) { | ||
// Test context. In testContext we have mocks functions. | ||
if (testContext && testContext.isTest) { | ||
passport = testContext.passport | ||
gatewayService = testContext.services | ||
} | ||
|
||
const secretOrKey = actionParams.secretOrPublicKeyFile ? | ||
fs.readFileSync(actionParams.secretOrPublicKeyFile, 'utf8') : actionParams.secretOrPublicKey | ||
|
||
passport.use(new JWTStrategy({ | ||
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), | ||
secretOrKey: secretOrKey, | ||
issuer: actionParams.issuer | ||
}, (jwtPayload, done) => { | ||
// At this point both the jwt signature, issuer and experation were validated | ||
// In addition, we have the jwt payload decoded and we can access its attributes | ||
// console.log(`JWT payload: ${JSON.stringify(jwtPayload)}`); | ||
|
||
// User validation. We expect to receive the username in the jwt 'sub' field | ||
if (!jwtPayload.sub) return done(null, false) | ||
|
||
gatewayService.auth | ||
.validateConsumer(jwtPayload.sub, {checkUsername: true}) | ||
.then((consumer) => { | ||
if (!consumer) { | ||
// invalid username or inactive user | ||
return done(null, false, {message: 'Invalid or inactive user'}) | ||
} | ||
return done(null, jwtPayload) // jwt successfully authenticated | ||
}) | ||
.catch((err) => { | ||
if (err.message === 'CREDENTIAL_NOT_FOUND') { | ||
return done(null, false, {message: 'User not found'}) | ||
} | ||
return done(err) | ||
}) | ||
})) | ||
|
||
return (req, res, next) => { | ||
passport.authenticate('jwt', {session: false}, (err, user, info) => { | ||
if (user) { | ||
req.user = user | ||
next() | ||
} | ||
|
||
if (info && info.message === 'No auth token') return res.status(401).send({ | ||
'code': 401, | ||
'message': 'UNAUTHORIZED', | ||
'description': 'Authentication failed for lack of authentication credentials.', | ||
'redirect_link': '/auth' | ||
}) | ||
|
||
if (info && info.message === 'Invalid or inactive user') return res.status(401).send({ | ||
'code': 401, | ||
'message': 'UNAUTHORIZED', | ||
'description': 'The token user is not properly registered as a consumer at the gateway.', | ||
'redirect_link': '/auth' | ||
}) | ||
|
||
if (info && info.message === 'jwt expired') return res.status(401).send({ | ||
'code': 401, | ||
'message': 'UNAUTHORIZED', | ||
'description': 'Authentication failed because access token is expired.', | ||
'redirect_link': '/auth' | ||
}) | ||
|
||
if (info && info.message === 'jwt issuer invalid. expected: ocariot') return res.status(401).send({ | ||
'code': 401, | ||
'message': 'UNAUTHORIZED', | ||
'description': 'Authentication failed because the access token contains invalid parameters.', | ||
'redirect_link': '/auth' | ||
}) | ||
|
||
if (info && info.message) return res.status(401).send({ | ||
'code': 401, | ||
'message': 'UNAUTHORIZED', | ||
'description': 'Authentication failed due to access token issues.', | ||
'redirect_link': '/auth' | ||
}) | ||
})(req, res, next) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* Policy to validate scopes | ||
*/ | ||
module.exports = { | ||
name: 'jwtScopes-policy', | ||
policy: require('./jwt-scopes'), | ||
schema: { | ||
$id: 'http://express-gateway.io/schemas/policies/jwtScopes-policy.json', | ||
type: 'object', | ||
properties: {} | ||
} | ||
} |
Oops, something went wrong.