diff --git a/packages/jwt/package.json b/packages/jwt/package.json index 5967f6bf110d2c7c27298e5cf572e8f71608d0e2..bbc73430e4a18d5d98d79c38d5b44be1dc35a4ec 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -1,6 +1,6 @@ { "name": "jsonql-jwt", - "version": "1.3.3", + "version": "1.3.4", "description": "jwt authentication helpers library for jsonql browser / node", "main": "main.js", "module": "index.js", @@ -38,10 +38,10 @@ "dependencies": { "colors": "^1.4.0", "fs-extra": "^8.1.0", - "jsonql-constants": "^1.8.5", - "jsonql-errors": "^1.1.3", + "jsonql-constants": "^1.8.10", + "jsonql-errors": "^1.1.5", "jsonql-params-validator": "^1.4.11", - "jsonql-utils": "^0.7.6", + "jsonql-utils": "^0.8.3", "jsonwebtoken": "^8.5.1", "jwt-decode": "^2.2.0", "socketio-jwt": "^4.5.0", @@ -52,13 +52,13 @@ }, "devDependencies": { "socket.io-client": "^2.3.0", - "ws": "^7.1.2", + "ws": "^7.2.0", "ava": "^2.4.0", "debug": "^4.1.1", "esm": "^3.2.25", - "koa": "^2.10.0", - "rollup": "^1.24.0", - "rollup-plugin-alias": "^2.0.1", + "koa": "^2.11.0", + "rollup": "^1.26.4", + "rollup-plugin-alias": "^2.2.0", "rollup-plugin-async": "^1.2.0", "rollup-plugin-buble": "^0.19.8", "rollup-plugin-bundle-size": "^1.0.3", diff --git a/packages/jwt/src/server/auth/create-token-validator.js b/packages/jwt/src/server/auth/create-token-validator.js index 7f906e4fbaf911ffe3cffb5b2032b802faeaca95..4273ba65fd6a16b484ea655211b10e2bfa84b68b 100644 --- a/packages/jwt/src/server/auth/create-token-validator.js +++ b/packages/jwt/src/server/auth/create-token-validator.js @@ -1,5 +1,5 @@ const { join, resolve } = require('path') -const { JsonqlValidatorError } = require('jsonql-errors') +const { JsonqlValidationError } = require('jsonql-errors') const { isString } = require('jsonql-params-validator') const { RSA_ALGO, HSA_ALGO } = require('jsonql-constants') const jwtDecode = require('../../jwt/jwt-decode') @@ -22,7 +22,7 @@ module.exports = function createTokenValidator(config) { opts = { algorithms: RSA_ALGO } } if (!key) { - throw new JsonqlValidatorError(`key is not provided!`) + throw new JsonqlValidationError(`key is not provided!`) } return function tokenValidator(token) { return jwtDecode(token, key, opts) diff --git a/packages/koa/package.json b/packages/koa/package.json index 4cd16f0398f7816fc55c400a6432ceb5fc03f0db..a91956edf1f305e60c6abbc96d010d7a20c45305 100644 --- a/packages/koa/package.json +++ b/packages/koa/package.json @@ -1,6 +1,6 @@ { "name": "jsonql-koa", - "version": "1.4.8", + "version": "1.4.9", "description": "jsonql Koa middleware", "main": "main.js", "module": "index.js", @@ -76,7 +76,7 @@ "jsonql-constants": "^1.8.10", "jsonql-contract": "^1.8.4", "jsonql-errors": "^1.1.5", - "jsonql-jwt": "^1.3.3", + "jsonql-jwt": "^1.3.4", "jsonql-node-client": "^1.2.2", "jsonql-params-validator": "^1.4.11", "jsonql-resolver": "^0.9.4", diff --git a/packages/koa/src/middlewares/auth-middleware.js b/packages/koa/src/middlewares/auth-middleware.js index 52829346281504a5f80a59e9f0d30fb1319f642c..c3d729272b6cc7eebb4b2de7ba97d03635aa9273 100644 --- a/packages/koa/src/middlewares/auth-middleware.js +++ b/packages/koa/src/middlewares/auth-middleware.js @@ -1,119 +1,21 @@ -// jsonql-auth middleware +// auth middleware +import { AUTH_TYPE, NOT_FOUND_STATUS } from 'jsonql-constants' +import { JsonqlResolverNotFoundError } from 'jsonql-errors' import { - AUTH_TYPE, - ISSUER_NAME, - VALIDATOR_NAME, - AUTH_CHECK_HEADER, - BEARER -} from 'jsonql-constants' -import { - chainFns, getDebug, - packResult, - headerParser, - printError, - isNotEmpty, - handleOutput, forbiddenHandler, ctxErrorHandler, - - createTokenValidator, // import from the jwt - + isNotEmpty, isObject } from '../utils' import { - JsonqlResolverNotFoundError, - JsonqlAuthorisationError, - JsonqlValidationError, - finalCatch -} from 'jsonql-errors' -// this method just search if the user provide their own validate token method -import { getLocalValidator } from 'jsonql-resolver' -import { trim } from 'lodash' - + authHeaderParser, + getToken, + getValidator +} from '../utils/auth-middleware-helpers' const debug = getDebug('auth-middleware') - -// this will create a cache version without keep calling the getter -var validatorFn; - -// declare a global variable to store the userdata with null value -// this way we don't mess up with the resolver have to check -// the last param is the user data - -/** - * @param {object} ctx Koa context - * @param {string} type to look for - * @return {mixed} the bearer token on success - */ -const authHeaderParser = ctx => { - // const header = headers[AUTH_CHECK_HEADER]; - let header = ctx.request.get(AUTH_CHECK_HEADER) - // debug(_header, AUTH_CHECK_HEADER); - // debug('Did we get the token?', header); - return header ? getToken(header) : false; -} - -/** - * just return the token string - * @param {string} header - * @return {string} token - */ -const getToken = header => { - return trim(header.replace(BEARER, '')) -} - -/** - * when using useJwt we allow the user to provide their own validator - * and we pass the result to this validator for them to do further processing - * This is useful because the user can control if they want to invalidate the client side - * from here based on their need - * @param {object} config configuration - * @param {function|boolean} validator false if there is none - * @return {function} the combine validator - */ -const createJwtValidatorChain = (config, validator = false) => { - const jwtFn = createTokenValidator(config) - if (!validator || typeof validator !== 'function') { - return jwtFn; - } - - return chainFns(jwtFn, validator) -} - -/** - * if useJwt = true then use the jsonql-jwt version - * @param {object} config configuration - * @param {string} type type of call - * @param {object} contract contract.json - * @return {function} the correct handler - */ -const getValidator = (config, type, contract) => { - if (validatorFn && typeof validatorFn === 'function') { - debug(`return the cache validatorFn`) - return validatorFn; - } - let localValidator; - try { - localValidator = getLocalValidator(config, type, contract) - } catch(e) { - const checkErr = e instanceof JsonqlResolverNotFoundError - // debug('checkErr', checkErr) - // we ignore this error becasue they might not have one? - if (!checkErr) { - return finalCatch(e) - } - } - // if (config.useJwt) { // we always use the jwt opiton now - ///debug(`return the jwt validation chain`) - return createJwtValidatorChain(config, localValidator) - //} - // debug(`return a local validator`) - // return localValidator; -} - /** * Auth middleware, we support - * 1) OAuth 2 * 2) JWT token * This is just front we don't do any real auth here, * instead we expect you to supply a function and pass you data and let you @@ -130,7 +32,7 @@ export default function authMiddleware(config) { const token = authHeaderParser(ctx) if (token) { debug('got a token', token) - validatorFn = getValidator(config , AUTH_TYPE, contract) + let validatorFn = getValidator(config , AUTH_TYPE, contract) let userdata = await validatorFn(token) debug('validatorFn result', userdata) if (isNotEmpty(userdata) && isObject(userdata)) { @@ -149,7 +51,7 @@ export default function authMiddleware(config) { } } catch(e) { if (e instanceof JsonqlResolverNotFoundError) { - return ctxErrorHandler(ctx, 404, e) + return ctxErrorHandler(ctx, NOT_FOUND_STATUS, e) } else { debug('throw at some where throw error', e) return forbiddenHandler(ctx, e) diff --git a/packages/koa/src/middlewares/public-method-middleware.js b/packages/koa/src/middlewares/public-method-middleware.js index c95483124fea7f067ff9edbbceb67082813565d7..801dedfd77d3054c887fc8a6ea0f19747775cb6f 100644 --- a/packages/koa/src/middlewares/public-method-middleware.js +++ b/packages/koa/src/middlewares/public-method-middleware.js @@ -19,7 +19,7 @@ export default function publicMethodMiddleware(opts) { return resolveMethod(ctx, resolverType, opts, contract) } } else if (resolverType === AUTH_TYPE && resolverName === opts.loginHandlerName) { - debug(`This is an auth ${opts.loginHandlerName} call`); + debug(`This is an auth ${opts.loginHandlerName} call`) return handleAuthMethods(ctx, resolverName, payload, opts, contract) } } diff --git a/packages/koa/src/options/process-jwt-keys.js b/packages/koa/src/options/process-jwt-keys.js index a610011942baa4f62e6c9c06c6fcf79f16ca613c..19fabd42e20fbb51e6916ccedc8627e0da906748 100644 --- a/packages/koa/src/options/process-jwt-keys.js +++ b/packages/koa/src/options/process-jwt-keys.js @@ -24,8 +24,8 @@ const getKeysFromCache = (ctx, config) => { // @TODO need to check the getter sequence, also allow supply a setter and getter const { setter, getter } = ctx.state.jsonql; if (config.enableAuth && - config.useJwt && - !isString(config.useJwt) && + config.useJwt && // @TODO need to remove this + !isString(config.useJwt) && // @TODO need to change this key name (!config.publicKey || !config.privateKey)) { if (typeof getter === 'function') { let privateKey = getter('privateKey') diff --git a/packages/koa/src/utils/auth-middleware-helpers.js b/packages/koa/src/utils/auth-middleware-helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..e72141363dd9ddcc13665528a728304ff162f53c --- /dev/null +++ b/packages/koa/src/utils/auth-middleware-helpers.js @@ -0,0 +1,106 @@ +// Taking the methods out from the auth-middleware to keep it simple +// jsonql-auth middleware +import { + AUTH_TYPE, + ISSUER_NAME, + VALIDATOR_NAME, + AUTH_CHECK_HEADER, + BEARER +} from 'jsonql-constants' +import { + chainFns, + getDebug, + forbiddenHandler, + ctxErrorHandler, + createTokenValidator, // import from the jwt + isObject +} from '../utils' +import { + JsonqlResolverNotFoundError, + JsonqlAuthorisationError, + JsonqlValidationError, + finalCatch +} from 'jsonql-errors' +// this method just search if the user provide their own validate token method +import { getLocalValidator } from 'jsonql-resolver' +import { trim } from 'lodash' + +const debug = getDebug('auth-middleware-helpers') + +// this will create a cache version without keep calling the getter +var validatorFn; + +// declare a global variable to store the userdata with null value +// this way we don't mess up with the resolver have to check +// the last param is the user data + +/** + * @param {object} ctx Koa context + * @param {string} type to look for + * @return {mixed} the bearer token on success + */ +export const authHeaderParser = ctx => { + // const header = headers[AUTH_CHECK_HEADER]; + let header = ctx.request.get(AUTH_CHECK_HEADER) + // debug(_header, AUTH_CHECK_HEADER); + // debug('Did we get the token?', header); + return header ? getToken(header) : false; +} + +/** + * just return the token string + * @param {string} header + * @return {string} token + */ +export const getToken = header => trim(header.replace(BEARER, '')) + + +/** + * when using useJwt we allow the user to provide their own validator + * and we pass the result to this validator for them to do further processing + * This is useful because the user can control if they want to invalidate the client side + * from here based on their need + * @param {object} config configuration + * @param {function|boolean} validator false if there is none + * @return {function} the combine validator + */ +const createJwtValidatorChain = (config, validator = false) => { + const jwtFn = createTokenValidator(config) + if (!validator || typeof validator !== 'function') { + return jwtFn; + } + + return chainFns(jwtFn, validator) +} + +/** + * if useJwt = true then use the jsonql-jwt version + * @param {object} config configuration + * @param {string} type type of call + * @param {object} contract contract.json + * @return {function} the correct handler + */ +export const getValidator = (config, type, contract) => { + if (validatorFn && typeof validatorFn === 'function') { + debug(`return the cache validatorFn`) + return validatorFn; + } + let localValidator; + try { + localValidator = getLocalValidator(config, type, contract) + } catch(e) { + debug('localValidator throw error', e) + const checkErr = e instanceof JsonqlResolverNotFoundError + // debug('checkErr', checkErr) + // we ignore this error becasue they might not have one? + if (!checkErr) { + return finalCatch(e) + } + } + if (config.useJwt) { // we always use the jwt opiton now + debug(`return the jwt validation chain`) + return createJwtValidatorChain(config, localValidator) + } + debug(`return a local validator`, localValidator) + return localValidator; +} diff --git a/packages/koa/tests/auth.test.js b/packages/koa/tests/auth.test.js index 9081738d2b043819f32b52bb3b3ca522a8c9befa..e153d7922ab2e62a91f27dfda44fd9d0e76a5099 100644 --- a/packages/koa/tests/auth.test.js +++ b/packages/koa/tests/auth.test.js @@ -13,23 +13,26 @@ const createServer = require('./helpers/server') const myKey = '4670994sdfkl'; const dir = 'auth'; const thisHeader = merge({}, {[contractKeyName]: myKey}, headers) +const keysDir = join(join(dirs.contractDir, 'auth-keys')) test.before((t) => { t.context.app = createServer({ useJwt: false, enableAuth: true, contractKey: myKey + // keysDir }, dir) }) test.after( () => { // remove the files after fsx.removeSync(join(dirs.contractDir, dir)) + // fsx.removeSync(keysDir) }) // Start running test(s) -test("Should NOT fail this Hello world test even I am not login", async t => { +test.serial("Should NOT fail this Hello world test even I am not login", async t => { let res = await superkoa(t.context.app) .post('/jsonql') .query({_cb: Date.now()}) @@ -40,7 +43,7 @@ test("Should NOT fail this Hello world test even I am not login", async t => { t.is(200, res.status) }) -test("The public-contract.json file should contain issuer information", async t => { +test.serial("The public-contract.json file should contain issuer information", async t => { let res = await superkoa(t.context.app) .get('/jsonql') .query({_cb: Date.now()}) @@ -50,7 +53,7 @@ test("The public-contract.json file should contain issuer information", async t }) -test('Should able to login with this credential', async t => { +test.serial('Should able to login with this credential', async t => { let res = await superkoa(t.context.app) .post('/jsonql') .query({_cb: Date.now()}) @@ -59,10 +62,11 @@ test('Should able to login with this credential', async t => { createQuery('login', ['nobody', myKey]) ); t.is(200, res.status) - t.is(bearer, res.body.data) + // we are not using the useJwt anymore, so the token is different from the key + // t.is(bearer, res.body.data) }) -test('Should cause a JsonqlAuthorisationError if I pass the wrong username or password', async t => { +test.serial('Should cause a JsonqlAuthorisationError if I pass the wrong username or password', async t => { let res = await superkoa(t.context.app) .post('/jsonql') .query({_cb: Date.now()}) @@ -76,7 +80,7 @@ test('Should cause a JsonqlAuthorisationError if I pass the wrong username or pa }) -test("It should able to call a method that is mark as public without login", async t => { +test.serial("It should able to call a method that is mark as public without login", async t => { // alwaysAvailable let res = await superkoa(t.context.app) .post('/jsonql') @@ -89,20 +93,20 @@ test("It should able to call a method that is mark as public without login", asy t.is('Hello there', res.body.data) }) -test('Now I should able to call the api with credential', async t => { +test.serial('Now I should able to call the api with credential', async t => { let res = await superkoa(t.context.app) .post('/jsonql') .query({_cb: Date.now()}) .set(merge({} , thisHeader, {Authorization: `Bearer ${bearer}`})) .send( createQuery('getUser', ['testing', 'userId']) - ); + ) t.is(200, res.status) // debug(res.body) t.is(1, res.body.data.userId) }) -test("Should NOT able to call the logout method without the Bearer", async t => { +test.serial("Should NOT able to call the logout method without the Bearer", async t => { let res = await superkoa(t.context.app) .post('/jsonql') .query({_cb: Date.now()}) diff --git a/packages/koa/tests/fixtures/resolvers/auth/login.js b/packages/koa/tests/fixtures/resolvers/auth/login.js index 5f8d1e79a01e4d6095bd571c1e4f105bf5e39e34..4b73763e18fe4725d1a3f3db19c6df61827be562 100644 --- a/packages/koa/tests/fixtures/resolvers/auth/login.js +++ b/packages/koa/tests/fixtures/resolvers/auth/login.js @@ -1,5 +1,5 @@ -const debug = require('debug')('jsonql:koa:issuer'); -const { bearer } = require('../../options'); +const debug = require('debug')('jsonql:koa:issuer') +const { bearer } = require('../../options') // this is the stock method for giving out author header @@ -10,7 +10,7 @@ const { bearer } = require('../../options'); * @return {string|boolean} token on success, false on fail */ module.exports = function(username, password) { - debug('received this', username, password); + debug('received this', username, password) if (username === 'nobody' && password) { return bearer; } diff --git a/packages/koa/tests/fixtures/resolvers/auth/validator.js b/packages/koa/tests/fixtures/resolvers/auth/validator.js index b588aa81ac6568822a903dd36386e08223ae3f57..939af888f7756cd3a5e0846c8cfb93dc719a4717 100644 --- a/packages/koa/tests/fixtures/resolvers/auth/validator.js +++ b/packages/koa/tests/fixtures/resolvers/auth/validator.js @@ -1,14 +1,14 @@ -const debug = require('debug')('jsonql:koa:validator'); -const { bearer } = require('../../options'); +const debug = require('debug')('jsonql:koa:validator') +const { bearer } = require('../../options') // this is the core method for checking the Auth header /** * @param {string} token jwt * @return {object|boolean} user data on success, false on fail */ -module.exports = function(token) { - debug('received this token', token, bearer); +module.exports = function validator(token) { + debug('received this token', token, bearer) if (token === bearer) { - return {userId: 1}; + return {userId: 1} } return false; } diff --git a/packages/resolver/src/handle-auth-methods.js b/packages/resolver/src/handle-auth-methods.js index 38d416a96af23465a44f10cc4ea789e3abdb01e8..33b3d4431afc31fa8c8eecc6e6f4bc5518626c25 100644 --- a/packages/resolver/src/handle-auth-methods.js +++ b/packages/resolver/src/handle-auth-methods.js @@ -1,9 +1,4 @@ // Auth methods handler -const { getDebug } = require('./utils') -const { searchResolvers } = require('./search-resolvers') -const { validateAndCall } = require('./validate-and-call') - -const { handleOutput, packResult, ctxErrorHandler } = require('jsonql-utils') const { QUERY_NAME, MUTATION_NAME, @@ -20,6 +15,11 @@ const { getErrorNameByInstance, UNKNOWN_ERROR } = require('jsonql-errors') +const { handleOutput, packResult, ctxErrorHandler } = require('jsonql-utils') + +const { getDebug } = require('./utils') +const { searchResolvers } = require('./search-resolvers') +const { validateAndCall } = require('./validate-and-call') const debug = getDebug('public-method-middleware')