diff --git a/package-lock.json b/package-lock.json index 3898093..1b36582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6584,7 +6584,8 @@ }, "ini": { "version": "1.3.5", - "resolved": "", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { @@ -6787,7 +6788,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } @@ -13521,6 +13523,45 @@ "ms": "2.1.2" } }, + "engine.io": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", + "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" + } + }, + "engine.io-client": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.3.tgz", + "integrity": "sha512-C3JYYyvna0FvSKOWGWpo0/EQ6MayO8uEKam1oedLZRpxLego9Fk6K3UH/Phieu1xPHauM7YqAygJ+6SraVc0Qg==", + "requires": { + "base64-arraybuffer": "0.1.4", + "component-emitter": "~1.3.0", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.1", + "has-cors": "1.1.0", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "requires": { + "base64-arraybuffer": "0.1.4" + } + }, "ipfs-utils": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/ipfs-utils/-/ipfs-utils-6.0.4.tgz", @@ -13576,6 +13617,56 @@ "version": "3.1.22", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==" + }, + "socket.io-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==" + }, + "socket.io-client-next": { + "version": "npm:socket.io-client@3.1.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.3.tgz", + "integrity": "sha512-4sIGOGOmCg3AOgGi7EEr6ZkTZRkrXwub70bBB/F0JSkMOUFpA77WsL87o34DffQQ31PkbMUIadGOk+3tx1KGbw==", + "requires": { + "@types/component-emitter": "^1.2.10", + "backo2": "~1.0.2", + "component-emitter": "~1.3.0", + "debug": "~4.3.1", + "engine.io-client": "~4.1.0", + "parseuri": "0.0.6", + "socket.io-parser": "~4.0.4" + } + }, + "socket.io-next": { + "version": "npm:socket.io@3.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", + "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", + "requires": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" + } + }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + } + }, + "ws": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" } } }, @@ -19983,138 +20074,6 @@ } } }, - "socket.io-client-next": { - "version": "npm:socket.io-client@3.1.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.3.tgz", - "integrity": "sha512-4sIGOGOmCg3AOgGi7EEr6ZkTZRkrXwub70bBB/F0JSkMOUFpA77WsL87o34DffQQ31PkbMUIadGOk+3tx1KGbw==", - "requires": { - "@types/component-emitter": "^1.2.10", - "backo2": "~1.0.2", - "component-emitter": "~1.3.0", - "debug": "~4.3.1", - "engine.io-client": "~4.1.0", - "parseuri": "0.0.6", - "socket.io-parser": "~4.0.4" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "engine.io-client": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.2.tgz", - "integrity": "sha512-1mwvwKYMa0AaCy+sPgvJ/SnKyO5MJZ1HEeXfA3Rm/KHkHGiYD5bQVq8QzvIrkI01FuVtOdZC5lWdRw1BGXB2NQ==", - "requires": { - "base64-arraybuffer": "0.1.4", - "component-emitter": "~1.3.0", - "debug": "~4.3.1", - "engine.io-parser": "~4.0.1", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~7.4.2", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - } - }, - "engine.io-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", - "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", - "requires": { - "base64-arraybuffer": "0.1.4" - } - }, - "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", - "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - } - }, - "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" - } - } - }, - "socket.io-next": { - "version": "npm:socket.io@3.1.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", - "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", - "requires": { - "@types/cookie": "^0.4.0", - "@types/cors": "^2.8.8", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.1", - "engine.io": "~4.1.0", - "socket.io-adapter": "~2.1.0", - "socket.io-parser": "~4.0.3" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "engine.io": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", - "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", - "requires": { - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~4.0.0", - "ws": "~7.4.2" - } - }, - "engine.io-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", - "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", - "requires": { - "base64-arraybuffer": "0.1.4" - } - }, - "socket.io-adapter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", - "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==" - }, - "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", - "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - } - }, - "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" - } - } - }, "socket.io-parser": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", diff --git a/src/lib/users.js b/src/lib/users.js index ee55d6c..fcb3a64 100644 --- a/src/lib/users.js +++ b/src/lib/users.js @@ -145,6 +145,34 @@ class UserLib { throw err } } + + // Used to authenticate a user. If the login and password salt match a user in + // the database, then it returns the user model. The Koa REST API uses the + // Passport library for this functionality. This function is used to + // authenticate users who login via the JSON RPC. + async authUser (login, passwd) { + try { + // console.log('login: ', login) + // console.log('passwd: ', passwd) + + const user = await UserModel.findOne({ email: login }) + if (!user) { + throw new Error('User not found') + } + + const isMatch = await user.validatePassword(passwd) + + if (!isMatch) { + throw new Error('Login credential do not match') + } + + return user + } catch (err) { + // console.error('Error in users.js/authUser()') + console.log('') + throw err + } + } } module.exports = UserLib diff --git a/src/modules/auth/controller.js b/src/modules/auth/controller.js index 294273f..b888c34 100644 --- a/src/modules/auth/controller.js +++ b/src/modules/auth/controller.js @@ -59,6 +59,8 @@ class Auth { */ async authUser (ctx, next) { try { + // Retrieve the user from the database after they've proven the correct + // password. const user = await _this.passport.authUser(ctx, next) if (!user) { ctx.throw(401) diff --git a/src/rpc/auth/index.js b/src/rpc/auth/index.js new file mode 100644 index 0000000..01828f2 --- /dev/null +++ b/src/rpc/auth/index.js @@ -0,0 +1,57 @@ +/* + This is the JSON RPC router for the users API +*/ + +// Public npm libraries +const jsonrpc = require('jsonrpc-lite') + +// Local libraries +const AuthLib = require('../../lib/auth') + +class AuthRPC { + constructor (localConfig) { + // Encapsulate dependencies + this.authLib = new AuthLib() + this.jsonrpc = jsonrpc + } + + // Top-level router for this library. All other methods in this class are for + // a specific endpoint. This method routes incoming calls to one of those + // methods. + async authRouter (rpcData) { + try { + console.log('authRouter rpcData: ', rpcData) + + // if (rpcData.payload.method === 'getAll') return await this.getAll() + + // Route the call based on the value of the method property. + switch (rpcData.payload.method) { + case 'getAll': + return await this.getAll() + case 'getUser': + return await this.getUser(rpcData) + } + } catch (err) { + console.error('Error in UsersRPC/rpcRouter()') + throw err + } + } + + // Get all Users. + async getAll () { + try { + console.log('Executing get all') + const users = await this.userLib.getAllUsers() + + const retJson = this.jsonrpc.success('getAll', users) + const retStr = JSON.stringify(retJson, null, 2) + + return retStr + } catch (err) { + console.error('Error in getAll()') + throw err + } + } +} + +module.exports = AuthRPC diff --git a/src/rpc/index.js b/src/rpc/index.js index ee4b614..a519af7 100644 --- a/src/rpc/index.js +++ b/src/rpc/index.js @@ -6,8 +6,9 @@ const jsonrpc = require('jsonrpc-lite') // Local support libraries -const UserController = require('./users') const wlogger = require('../lib/wlogger') +const UserController = require('./users') +// const AuthController = require('./auth') let _this @@ -52,6 +53,9 @@ class JSONRPC { case 'users': retStr = await _this.userController.userRouter(parsedData) break + case 'auth': + retStr = await _this.authController.authRouter(parsedData) + break } return retStr diff --git a/src/rpc/users/index.js b/src/rpc/users/index.js index 07e8e60..d313266 100644 --- a/src/rpc/users/index.js +++ b/src/rpc/users/index.js @@ -15,11 +15,22 @@ class UserRPC { this.jsonrpc = jsonrpc } + // Top-level router for this library. All other methods in this class are for + // a specific endpoint. This method routes incoming calls to one of those + // methods. async userRouter (rpcData) { try { console.log('userRouter rpcData: ', rpcData) - if (rpcData.payload.method === 'getAll') return await this.getAll() + // if (rpcData.payload.method === 'getAll') return await this.getAll() + + // Route the call based on the value of the method property. + switch (rpcData.payload.method) { + case 'getAll': + return await this.getAll() + case 'getUser': + return await this.getUser(rpcData) + } } catch (err) { console.error('Error in UsersRPC/rpcRouter()') throw err diff --git a/test/unit/biz-logic/a02-users.lib-unit.js b/test/unit/biz-logic/a02-users.lib-unit.js index 2885e2f..507b171 100644 --- a/test/unit/biz-logic/a02-users.lib-unit.js +++ b/test/unit/biz-logic/a02-users.lib-unit.js @@ -26,10 +26,13 @@ describe('#users', () => { console.log(`Connecting to database: ${config.database}`) mongoose.Promise = global.Promise mongoose.set('useCreateIndex', true) // Stop deprecation warning. - await mongoose.connect(config.database, { - useUnifiedTopology: true, - useNewUrlParser: true - }) + await mongoose.connect( + config.database, + { + useUnifiedTopology: true, + useNewUrlParser: true + } + ) // Delete all previous users in the database. await testUtils.deleteAllUsers() @@ -312,7 +315,10 @@ describe('#users', () => { assert.fail('Unexpected code path') } catch (err) { // console.log(err) - assert.include(err.message, "Property 'type' can only be changed by Admin user") + assert.include( + err.message, + "Property 'type' can only be changed by Admin user" + ) } }) @@ -336,6 +342,39 @@ describe('#users', () => { // TODO: verify that an admin can change the type of a user }) + describe('#authUser', () => { + it('should return a user db model after successful authentication', async () => { + const user = await uut.authUser('test@test.com', 'password') + // console.log('user: ', user) + + assert.property(user, '_id') + assert.property(user, 'email') + assert.property(user, 'name') + }) + + it('should throw an error if no user matches the login', async () => { + try { + await uut.authUser('noone@nowhere.com', 'password') + // console.log('user: ', user) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'User not found') + } + }) + + it('should throw an error if password does not match', async () => { + try { + await uut.authUser('test@test.com', 'badpassword') + // console.log('user: ', user) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'Login credential do not match') + } + }) + }) + describe('#deleteUser', () => { it('should throw error if no user provided', async () => { try {