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
16 changed files
with
4,220 additions
and
39 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 |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"env": { | ||
"es6": true, | ||
"node": true, | ||
"mocha": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": 2017 | ||
}, | ||
"extends": "eslint:recommended", | ||
"rules": { | ||
"indent": [ | ||
"error", | ||
2 | ||
], | ||
"linebreak-style": [ | ||
"error", | ||
"unix" | ||
], | ||
"quotes": [ | ||
"error", | ||
"single" | ||
] | ||
} | ||
} |
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,61 +1,113 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
*.pid.lock | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# nyc test coverage | ||
.nyc_output | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# Bower dependency directory (https://bower.io/) | ||
bower_components | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled binary addons (https://nodejs.org/api/addons.html) | ||
# Compiled binary addons (http://nodejs.org/api/addons.html) | ||
build/Release | ||
|
||
# Dependency directories | ||
node_modules/ | ||
jspm_packages/ | ||
|
||
# TypeScript v1 declaration files | ||
typings/ | ||
|
||
# Optional npm cache directory | ||
.npm | ||
# Dependency directory | ||
# Commenting this out is preferred by some people, see | ||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- | ||
node_modules | ||
|
||
# Optional eslint cache | ||
.eslintcache | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
# Output of 'npm pack' | ||
*.tgz | ||
|
||
# Yarn Integrity file | ||
.yarn-integrity | ||
|
||
# dotenv environment variables file | ||
.env | ||
# Users Environment Variables | ||
.lock-wscript | ||
|
||
# next.js build output | ||
.next | ||
# Certificates | ||
*.pem | ||
*.key | ||
*.crt | ||
|
||
# IDEs and editors (shamelessly copied from @angular/cli's .gitignore) | ||
.idea/ | ||
.project | ||
.classpath | ||
.c9/ | ||
*.launch | ||
.settings/ | ||
*.sublime-workspace | ||
|
||
# IDE - VSCode | ||
.vscode/* | ||
!.vscode/settings.json | ||
!.vscode/tasks.json | ||
!.vscode/launch.json | ||
!.vscode/extensions.json | ||
|
||
### Linux ### | ||
*~ | ||
|
||
# temporary files which can be created if a process still has a handle open of a deleted file | ||
.fuse_hidden* | ||
|
||
# KDE directory preferences | ||
.directory | ||
|
||
# Linux trash folder which might appear on any partition or disk | ||
.Trash-* | ||
|
||
# .nfs files are created when an open file is removed but is still being accessed | ||
.nfs* | ||
|
||
### OSX ### | ||
*.DS_Store | ||
.AppleDouble | ||
.LSOverride | ||
|
||
# Icon must end with two \r | ||
Icon | ||
|
||
# Thumbnails | ||
._* | ||
|
||
# Files that might appear in the root of a volume | ||
.DocumentRevisions-V100 | ||
.fseventsd | ||
.Spotlight-V100 | ||
.TemporaryItems | ||
.Trashes | ||
.VolumeIcon.icns | ||
.com.apple.timemachine.donotpresent | ||
|
||
# Directories potentially created on remote AFP share | ||
.AppleDB | ||
.AppleDesktop | ||
Network Trash Folder | ||
Temporary Items | ||
.apdisk | ||
|
||
### Windows ### | ||
# Windows thumbnail cache files | ||
Thumbs.db | ||
ehthumbs.db | ||
ehthumbs_vista.db | ||
|
||
# Folder config file | ||
Desktop.ini | ||
|
||
# Recycle Bin used on file shares | ||
$RECYCLE.BIN/ | ||
|
||
# Windows Installer files | ||
*.cab | ||
*.msi | ||
*.msm | ||
*.msp | ||
|
||
# Windows shortcuts | ||
*.lnk |
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,10 @@ | ||
.editorconfig | ||
.jshintrc | ||
.travis.yml | ||
.istanbul.yml | ||
.babelrc | ||
.idea/ | ||
.vscode/ | ||
test/ | ||
coverage/ | ||
.github/ |
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,11 @@ | ||
sudo: false | ||
language: node_js | ||
node_js: | ||
- 'node' | ||
- '8' | ||
before_install: | ||
- npm i -g nyc coveralls | ||
script: | ||
- npm test | ||
after_success: | ||
- npm run coverage |
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,2 +1,7 @@ | ||
# feathers-auth0-authorize-hook | ||
# FeathersJS Auth0 Authorization Hook | ||
|
||
[![Build Status](https://travis-ci.org/morphatic/feathers-auth0-authorize-hook.svg?branch=master)](https://travis-ci.org/morphatic/feathers-auth0-authorize-hook) | ||
[![Coverage Status](https://coveralls.io/repos/github/morphatic/feathers-auth0-authorize-hook/badge.svg?branch=master)](https://coveralls.io/github/morphatic/feathers-auth0-authorize-hook?branch=master) | ||
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/morphatic/astrologyjs/master/LICENSE) | ||
|
||
A "before" hook for FeathersJS to authorize requests accompanied by an Auth0-issued JWT. |
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,16 @@ | ||
{ | ||
"host": "localhost", | ||
"port": 3030, | ||
"jwksUri": "https://example.auth0.com/.well-known/jwks.json", | ||
"jwtOptions": { | ||
"algorithms": [ | ||
"RS256" | ||
], | ||
"audience": [ | ||
"https://example.auth0.com/api/v2/", | ||
"https://example.auth0.com/userinfo" | ||
], | ||
"ignoreExpiration": false, | ||
"issuer": "https://example.auth0.com/" | ||
} | ||
} |
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,161 @@ | ||
/** | ||
* Checks the `Authorization` header for a JWT and verifies | ||
* that it is legitimate and valid before allowing the | ||
* request to proceed. | ||
*/ | ||
|
||
const errors = require('@feathersjs/errors') | ||
const jwt = require('jsonwebtoken') | ||
const rp = require('request-promise') | ||
|
||
module.exports = ({ | ||
// These two parameters allow users to customize which models/services | ||
// they want to use with this hook | ||
userService = 'users', | ||
keysService = 'keys', | ||
// the actual hook | ||
authorize = async (context) => { | ||
// Throw if the hook is being called from an unexpected location. | ||
if (context.type !== 'before') | ||
throw new errors.NotAuthenticated('`authorize()` can only be used as a `before` hook.', context) | ||
|
||
// get the Authorization header | ||
const header = (context.params.headers || {}).authorization || null | ||
|
||
// throw an error if the Authorization header is not set | ||
if (!header) throw new errors.NotAuthenticated('`Authorization` header not set.', context) | ||
|
||
// extract the raw token from the header | ||
const currentToken = header.replace('Bearer ', '').trim() | ||
|
||
// decode it | ||
let token = jwt.decode(currentToken, { complete: true }) | ||
|
||
// throw an error if the token was malformed or missing | ||
if (!token) throw new errors.NotAuthenticated('The token was malformed or missing.') | ||
|
||
// get the user ID from the token payload | ||
const user_id = token.payload.sub | ||
|
||
// check to see if we have a member with this ID in the database | ||
let member | ||
try { | ||
member = await context.app.service(userService).find({ | ||
paginate: false, | ||
query: { user_id, $limit: 1 } | ||
}).then(results => { | ||
if (results[0]) return results[0] | ||
throw 'member does not exist' | ||
}) | ||
} catch (err) { | ||
// throw an error if no such member exists | ||
throw new errors.NotAuthenticated('No member with this ID exists.', context) | ||
} | ||
|
||
// if the member already has a valid, current token, stop here | ||
if (member.currentToken && member.currentToken === currentToken) return context | ||
|
||
// otherwise, get the kid from the token header | ||
const kid = token.header.kid | ||
|
||
// create a JWKS retrieval client | ||
const client = getJWKS(context.app.get('jwksUri')) | ||
|
||
// get the signing key from the JWKS endpoint at Auth0 | ||
const key = await getKey(kid, context.app.service(keysService), client) | ||
|
||
// verify the raw JWT | ||
try { | ||
jwt.verify(currentToken, key, context.app.get('jwtOptions')) | ||
} catch (err) { | ||
throw new errors.NotAuthenticated('Token could not be verified.', err.message) | ||
} | ||
|
||
// OK! The JWT is valid, store it in the member profile | ||
// (It's okay if this fails) | ||
context.app.service(userService).patch( | ||
null, | ||
{ currentToken }, | ||
{ query: { user_id: member.user_id } } | ||
) | ||
|
||
// If we made it this far, we're all good! | ||
return context | ||
}, | ||
/** | ||
* Takes a JWKS endpoint URI and returns a function that can retrieve an | ||
* array of JWKs, i.e. a JWKS. The resulting function may throw any of | ||
* [the errors described here]{@link https://github.com/request/promise-core/blob/master/lib/errors.js} | ||
* | ||
* @param {string} uri The URI of the JWKS endpoint | ||
* @returns {function} A function that can retrieve a JWKS from the endpoint | ||
*/ | ||
getJWKS = uri => () => rp({ uri, json: true }), | ||
/** | ||
* Takes a JWK object and returns a valid key in PEM format. Throws | ||
* a GeneralError if there are no x5c items stored on the JWK. | ||
* | ||
* @param {string} jwk The JWK to be parsed | ||
* @returns {string} The key in PEM format from the first x5c entry | ||
* @throws {GeneralError} Throws a GeneralError if there are no x5c items | ||
*/ | ||
x5cToPEM = jwk => { | ||
if (!jwk.x5c.length > 0) throw new errors.GeneralError('Stored JWK has no x5c property.') | ||
const lines = jwk.x5c[0].match(/.{1,64}/g).join('\n') | ||
return `-----BEGIN CERTIFICATE-----\n${lines}\n-----END CERTIFICATE-----\n` | ||
}, | ||
/** | ||
* Takes a `kid`, a reference to an in-memory Feathers service (`svc`) | ||
* for storing JWKs, and a `client` for retrieving signing keys from a | ||
* JWKS endpoint. Returns a valid signing key in PEM format or throws | ||
* a `SigningKeyNotFoundError`. If a key is successfully retrieved from | ||
* the endpoint, it tries to store this value using the `svc`. | ||
* | ||
* @async | ||
* @param {string} kid The `kid` for the JWK to be retrieved | ||
* @param {object} svc The Feathers service used to store JWKs in memory | ||
* @param {function} jwksClient A function that takes a `kid` and returns a key | ||
* @returns {string} The retrieved signing key in PEM format | ||
* @throws {GeneralError} Thrown by the `client` if `kid` is not found | ||
*/ | ||
getKey = async (kid, svc, jwksClient) => { | ||
try { | ||
// get the signing key from the in-memory service, if it exists | ||
const storedKey = await svc.get(kid) | ||
|
||
// if the storedKey exists, return it | ||
if (storedKey) return x5cToPEM(storedKey) | ||
} catch (err) { | ||
// nothing to see here. please move along... | ||
} | ||
|
||
// otherwise, we need to get it from our JWKS endpoint | ||
let jwk | ||
try { | ||
const jwks = await jwksClient() | ||
jwk = jwks.keys.find(k => k.kid === kid) | ||
} catch (err) { | ||
// throw an error if we still don't have a signing key | ||
throw new errors.GeneralError('Could not retrieve JWKS', err) | ||
} | ||
|
||
// throw an error if there were no JWKs that contained our kid | ||
if (!jwk) throw new errors.GeneralError('Could not find a JWK matching given kid') | ||
|
||
// get the signing key from the retrieved JWK | ||
const key = x5cToPEM(jwk) | ||
|
||
// store the jwk in our in-memory service | ||
try { svc.create(jwk) } catch (e) { /* no problem if this fails */ } | ||
|
||
// and return the key | ||
return key | ||
} | ||
} = {}) => ({ | ||
userService, | ||
keysService, | ||
authorize, | ||
getJWKS, | ||
x5cToPEM, | ||
getKey | ||
}) |
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,3 @@ | ||
const authorize = require('./authorize') | ||
|
||
module.exports = authorize |
Oops, something went wrong.