diff --git a/test/1-api-sync-mode-sqlite.spec.js b/test/1-api-sync-mode-sqlite.spec.js index 4cbbaea43..5b43858d2 100644 --- a/test/1-api-sync-mode-sqlite.spec.js +++ b/test/1-api-sync-mode-sqlite.spec.js @@ -27,7 +27,8 @@ const agent = request.agent(app) const { apiSyncModeSqliteTestCases, - signUpTestCase + signUpTestCase, + removeUserTestCases } = require('./test-cases') let wrkReportServiceApi = null @@ -94,4 +95,6 @@ describe('Sync mode API with SQLite', () => { signUpTestCase(agent, params) apiSyncModeSqliteTestCases(agent, params) + signUpTestCase(agent, params) + removeUserTestCases(agent, params) }) diff --git a/test/4-sub-account.spec.js b/test/4-sub-account.spec.js index 379be2189..cc9508b7c 100644 --- a/test/4-sub-account.spec.js +++ b/test/4-sub-account.spec.js @@ -32,7 +32,8 @@ const agent = request.agent(app) const { apiSyncModeSqliteTestCases, additionalApiSyncModeSqliteTestCases, - signUpTestCase + signUpTestCase, + removeUserTestCases } = require('./test-cases') let wrkReportServiceApi = null @@ -429,5 +430,10 @@ describe('Sub-account', () => { additionalApiSyncModeSqliteTestCases(agent, params) }) + describe('Removing sub-account API', () => { + before(beforeFn) + + removeUserTestCases(agent, params) + }) }) }) diff --git a/test/test-cases/api-sync-mode-sqlite-test-cases.js b/test/test-cases/api-sync-mode-sqlite-test-cases.js index 4e08bf703..e90ce6f35 100644 --- a/test/test-cases/api-sync-mode-sqlite-test-cases.js +++ b/test/test-cases/api-sync-mode-sqlite-test-cases.js @@ -3354,7 +3354,7 @@ module.exports = ( assert.isNotOk(res.body.result) }) - it('it should be successfully performed by the removeUser method', async function () { + it('it should be successfully performed by the removeUser method with token', async function () { this.timeout(5000) const res = await agent diff --git a/test/test-cases/index.js b/test/test-cases/index.js index 4470bccb9..dbbdce10f 100644 --- a/test/test-cases/index.js +++ b/test/test-cases/index.js @@ -8,10 +8,12 @@ const additionalApiSyncModeSqliteTestCases = require( ) const signUpTestCase = require('./sign-up-test-case') const getSyncProgressTestCase = require('./get-sync-progress-test-case') +const removeUserTestCases = require('./remove-user-test-cases') module.exports = { apiSyncModeSqliteTestCases, additionalApiSyncModeSqliteTestCases, signUpTestCase, - getSyncProgressTestCase + getSyncProgressTestCase, + removeUserTestCases } diff --git a/test/test-cases/remove-user-test-cases.js b/test/test-cases/remove-user-test-cases.js new file mode 100644 index 000000000..dea3107e0 --- /dev/null +++ b/test/test-cases/remove-user-test-cases.js @@ -0,0 +1,91 @@ +'use strict' + +const { assert } = require('chai') + +module.exports = ( + agent, + params = {} +) => { + const { + basePath, + auth: { + email, + password, + isSubAccount + } + } = params + const auth = { token: '' } + + it('it should be successfully performed by the signIn method', async function () { + this.timeout(5000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth: { + email, + password, + isSubAccount + }, + method: 'signIn', + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + assert.isObject(res.body) + assert.propertyVal(res.body, 'id', 5) + assert.isObject(res.body.result) + assert.strictEqual(res.body.result.email, email) + assert.strictEqual(res.body.result.isSubAccount, isSubAccount) + assert.isString(res.body.result.token) + + auth.token = res.body.result.token + }) + + it('it should be successfully performed by the removeUser method with email', async function () { + this.timeout(5000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth: { + email, + password, + isSubAccount + }, + method: 'removeUser', + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + assert.isObject(res.body) + assert.propertyVal(res.body, 'id', 5) + assert.isBoolean(res.body.result) + assert.isOk(res.body.result) + }) + + it('it should not be successfully performed by the verifyUser method', async function () { + this.timeout(5000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + auth, + method: 'verifyUser', + id: 5 + }) + .expect('Content-Type', /json/) + .expect(401) + + assert.isObject(res.body) + assert.isObject(res.body.error) + assert.propertyVal(res.body.error, 'code', 401) + assert.propertyVal(res.body.error, 'message', 'Unauthorized') + assert.propertyVal(res.body, 'id', 5) + }) +} diff --git a/workers/loc.api/sync/authenticator/index.js b/workers/loc.api/sync/authenticator/index.js index f5f943d07..67e6f0dd0 100644 --- a/workers/loc.api/sync/authenticator/index.js +++ b/workers/loc.api/sync/authenticator/index.js @@ -52,6 +52,7 @@ class Authenticator { * It may only work for one grenache worker instance */ this.userSessions = new Map() + this.userTokenMapByEmail = new Map() } async signUp (args, opts) { @@ -302,7 +303,7 @@ class Authenticator { typeof token === 'string' ) ? token - : this.getUserSessionByEmail({ email, isSubAccount }).token + : this.getUserSessionByEmail({ email, isSubAccount })?.token const createdToken = ( existedToken && typeof existedToken === 'string' @@ -361,7 +362,11 @@ class Authenticator { throw new AuthError() } - this.removeUserSessionByToken(token) + this.removeUserSession({ + email, + isSubAccount, + token + }) if (isSchedulerEnabled) { await this.dao.updateRecordOf( @@ -510,7 +515,7 @@ class Authenticator { const existedToken = this.getUserSessionByEmail( { email, isSubAccount } - ).token + )?.token const createdToken = ( existedToken && typeof existedToken === 'string' @@ -602,7 +607,7 @@ class Authenticator { token, isReturnedPassword ) - const { apiKey, apiSecret } = { ...session } + const { apiKey, apiSecret } = session ?? {} if ( !apiKey || @@ -830,15 +835,21 @@ class Authenticator { throw new UserRemovingError() } - this.removeUserSessionByToken(token) + this.removeUserSession({ + email, + isSubAccount, + token + }) return true } setUserSession (user) { const { token } = user ?? {} + const tokenKey = this._getTokenKeyByEmailField(user) this.userSessions.set(token, { ...user }) + this.userTokenMapByEmail.set(tokenKey, token) } getUserSessionByToken (token, isReturnedPassword) { @@ -847,18 +858,10 @@ class Authenticator { return pickSessionProps(session, isReturnedPassword) } - getUserSessionByEmail (args, isReturnedPassword) { - const { - email, - isSubAccount = false - } = args ?? {} - const keyVal = [...this.userSessions].find(([, session]) => { - return ( - email === session.email && - isSubAccount === session.isSubAccount - ) - }) - const session = Array.isArray(keyVal) ? keyVal[1] : {} + getUserSessionByEmail (user, isReturnedPassword) { + const tokenKey = this._getTokenKeyByEmailField(user) + const token = this.userTokenMapByEmail.get(tokenKey) + const session = this.userSessions.get(token) return pickSessionProps(session, isReturnedPassword) } @@ -873,8 +876,19 @@ class Authenticator { return new Map(sessionsMap) } - removeUserSessionByToken (token) { - return this.userSessions.delete(token) + removeUserSession (user) { + const { token } = user ?? {} + const tokenKey = this._getTokenKeyByEmailField(user) + const _token = ( + token && + typeof token === 'string' + ) + ? token + : this.userTokenMapByEmail.get(tokenKey) + + this.userTokenMapByEmail.delete(tokenKey) + + return this.userSessions.delete(_token) } async decryptApiKeys (password, users) { @@ -905,6 +919,18 @@ class Authenticator { return isArray ? res : res[0] } + + _getTokenKeyByEmailField (user) { + const { + email, + isSubAccount + } = user ?? {} + const suffix = isSubAccount + ? ':sub-account' + : '' + + return `${email}${suffix}` + } } decorateInjectable(Authenticator, depsTypes)